您的位置:首页 > 其它

SELECT和一次的INSERT/DELETE/UPDATE需要用事物管理?

2009-11-05 00:14 375 查看
SELECT和一次的INSERT/DELETE/UPDATE需要用事物管理?

事务(数据库引擎)
事务是作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元必须有四个属性,称为原子性、一致性、隔离性和持久性 (ACID) 属性,只有这样才能成为一个事务。
原子性
事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。
一致性
事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。
隔离
由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务识别数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是第二个事务修改它之后的状态,事务不会识别中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。
持久性
事务完成之后,它对于系统的影响是永久性的。该修改即使出现系统故障也将一直保持。
控制事务(数据库引擎)
应用程序主要通过指定事务启动和结束的时间来控制事务。可以使用 Transact-SQL 语句或数据库应用程序编程接口 (API) 函数来指定这些时间。系统还必须能够正确处理那些在事务完成之前便终止事务的错误。
默认情况下,事务按连接级别进行管理。在一个连接上启动一个事务后,该事务结束之前,在该连接上执行的所有 Transact-SQL 语句都是该事务的一部分。但是,在多个活动的结果集 (MARS) 会话中,Transact-SQL 显式或隐式事务将变成批范围的事务,这种事务按批处理级别进行管理。当批处理完成时,如果批范围的事务还没有提交或回滚,SQL Server 将自动回滚该事务。
1、启动事务
显式事务
通过 API 函数或通过发出 Transact-SQL BEGIN TRANSACTION 语句来显式启动事务。
自动提交事务
数据库引擎的默认模式。每个单独的 Transact-SQL 语句都在其完成后提交。不必指定任何语句来控制事务。
隐式事务
通过 API 函数或 Transact-SQL SET IMPLICIT_TRANSACTIONS ON 语句,将隐性事务模式设置为打开。下一个语句自动启动一个新事务。当该事务完成时,下一个 Transact-SQL 语句又将启动一个新事务。
2、结束事务
您可以使用 COMMIT 或 ROLLBACK 语句,或者通过 API 函数来结束事务。
COMMIT
如果事务成功,则提交。COMMIT 语句保证事务的所有修改在数据库中都永久有效。COMMIT 语句还释放事务使用的资源(例如,锁)。
ROLLBACK
如果事务中出现错误,或用户决定取消事务,则回滚该事务。ROLLBACK 语句通过将数据返回到它在事务开始时所处的状态,来取消事务中的所有修改。ROLLBACK 还释放事务占用的资源。在程序中可以通过捕获异常(例外)监测事务。

http://www.javaeye.com/topic/1603?page=1上看到的一些评论,这里摘录下来:

我在Ibatis的代码中看到这样一段:

sqlMap.startTransaction();
list = sqlMap.executeQueryForList(statementName, parameterObject);; sqlMap.commitTransaction();
} catch (SQLException e); {
try {
sqlMap.rollbackTransaction();
sqlMap.startTransaction();;
list = sqlMap.executeQueryForList(statementName, parameterObject);;
sqlMap.commitTransaction();;
} catch (SQLException e); {
try {
sqlMap.rollbackTransaction();;

我以前见到用事务的都是同时处理几个表,只要其中一个操作失败,事务就回滚,但是这段代码和sql文件我都看了,的确只是针对一个表操作,这种情况用事务能带来什麽好处呢,直接写"list = sqlMap.executeQueryForList(statementName"不就行了吗,不把它放到事务中。想不明白,请大家指点!
Where on earth does everyone get the idea that readonly operations do not require transactions???? I would like to know where this idea originated from! I have never seen ANY book that advocates this. Where did this idea start?

我认为不需要加事务。其实Hibernate的Transaction就是JDBC Transaction而已,而实际上Hibernate并没有在Transaction上面附加关于PO的状态管理,因此你先问问你自己,假设你不用Hibernate,你只用JDBC写程序,你会在发送一次SQL Select之后进行conn.commit()吗?如果答案不是,那么你一样在Hibernate中不需要加Transaction,事实上我是从来不加的。而且由于数据库CRUD操作90%以上的都是只读的查询,因此如果你给只读查询加上事务控制,经过我的测试证明,速度会有稍微的降低,而数据库的CPU占用率会有明显的上升。

从数据库的角度来讲,做完Select后,Commit,数据库可以根据这些信息进行隔离级别的优化,有些数据库Manual是建议在Select后进行Commit的。

使用Transaction确实对性能降低不多,但是会非常明显增加数据库的CPU负载。启动不启动事务和数据库是否命中Cache没有什么关系。只要你提交commit,数据库是一定会提交事务的。而我们知道事务控制即使在数据库端也是比较复杂的。我的理解是对读查询提交事务也许可以保证数据的一致性。例如在多线程频繁读写一块数据的时候,也许就在你select出来的过程中,数据已经被其他写操作修改了。总之,除非那种对实时数据的精确性要求非常的环境,否则我是绝对不会对读查询使用Transaction的。

从这篇文章来看,将查询放到事务中,还是有道理的。
虽然并发度不高的程序,出错的概率很小,但是拿 程序正确运行的保证 和 少许效率相比,我倾向于前者,即便是在并发度不高的应用中。
=========================================
SQL 标准 用三个必须在并行的事务之间避免的现象定义了四个级别的事务隔离。 这些不希望发生的现象是:

脏读(dirty reads)
一个事务读取了另一个未提交的并行事务写的数据。

不可重复读(non-repeatable reads)
一个事务重新读取前面读取过的数据, 发现该数据已经被另一个已提交的事务修改过。

幻读(phantom read)
一个事务重新执行一个查询,返回一套符合查询条件的行, 发现这些行因为其他最近提交的事务而发生了改变。

这四种隔离级别和对应的行为在Table 9-1 里描述.

Table 9-1. SQL 隔离级别

隔离级别 脏读(Dirty Read) 不可重复读(NonRepeatable Read) 幻读(Phantom Read)
读未提交(Read uncommitted) 可能 可能 可能
读已提交(Read committed) 不可能 可能 可能
可重复读(Repeatable read) 不可能 不可能 可能
可串行化(Serializable ) 不可能 不可能 不可能

PostgreSQL 提供读已提交(read committed)和可串行化(serializable)隔离级别。

9.2.1. 读已提交隔离级别
读已提交(Read Committed) 是 PostgreSQL 里的缺省隔离级别。 当一个事务运行在这个隔离级别时, 一个 SELECT 查询只能看到查询开始之前提交的数据而永远 无法看到未提交的数据或者是在查询执行时其他并行的事务提交做的改变。 (不过 SELECT 的确看得见同一次事务中前面更新 的结果.即使它们还没提交也看得到.) 实际上,一个 SELECT 查询看到一个在该查询开始 运行的瞬间该数据库地一个快照。 请注意两个相邻的 SELECT 可能看到不同 的数据,哪怕它们是在同一个事务里,因为其它事务会在第一个 SELECT执行的时候提交.

UPDATE, DELETE, 或者 SELECT FOR UPDATE 在搜索目标行的时候的行为和 SELECT 一样:它们只能找到在查询开始的时候已经提交 的行。 不过,这样的目标行在被找到的时候可能已经被其它并发的事务更新(或者删除,或者标记为更新的)。 在这种情况下,即将进行的更新将等待第一个更新事务提交或者回滚(如果它还在处理)。 如果第一个更新回滚,那么它的作用将被忽略,而第二个更新者将继续更新最初发现的行。 如果第一个更新者提交,那么如果第一个更新者删除了该行,则第二个更新者将忽略该行, 否则它将视图在该行的已更新的版本上施加它的操作。系统将重新计算查询搜索条件 (WHERE 子句),看看该行已更新的办不那是否仍然符合搜索条件。如果是, 则第二个更新继续其操作,从该行的已更新版本开始。

因为上面的规则,正在更新的规则可能会看到不一致的快找 --- 它们可以看到 影响它们试图更新的并发更新查询的效果,但是它们看不到那些查询对数据库 里其它行的作用。这样的行为令读已提交模式不适合用于哪种涉及复杂搜索条件 的查询。不过,它对于简单的情况而言是正确的。比如,假设我们用类似下面这样 的查询更新银行余额:

BEGIN;
UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 12345;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 7534;
COMMIT;
如果两个并发事务试图修改帐号 12345 的余额,那我们很明显希望第二个 事务是从帐户行的已经更新过的版本上进行更新。因为每个查询只是影响 一个已经决定了的行,因此让它看到更新后的版本不会导致任何不一致的 问题。

因为在读已提交模式里,每个新的查询都是从一个新的快照开始的,而这个 快照包含所有到该时刻为止已经提交的事务,因此同一个事务里的后面的 查询将看到任何已提交的并发事务的效果。这里要考虑的问题是我们在 一个查询里是否看到数据库里绝对一致的视图。

读已提交模式提供的部分事务隔离对于许多应用而言是足够的,并且这个 模式速度快,使用简单。不过,对于做复杂查询和更新的查询,可能需要 保证数据库有比读已提交模式提供的更加严格的一致性视图。

9.2.2. 可串行化隔离级别
可串行化(Serializable) 提供最严格的事务隔离。 这个级别模拟串行的事务执行, 就好象事务将被一个接着一个那样串行的,而不是并行的执行。 不过,使用这个级别的应用必须准备在串行化失败的时候重新发动事务.

当一个事务处于可串行化级别, 一个 SELECT 查询只能看到在事务开始之前提交的数据而永远看不到未提交的 数据或事务执行中其他并行事务提交的修改。 (不过,SELECT 的确看得到同一次事务中 前面的更新的效果.即使事务还没有提交也一样.) 这个行为和读已提交级别是不太一样的,它的 SELECT 看到的是 该事务开始时的快照,而不是该事务内部当前查询开始时的快照. 这样,一个事务内部后面的 SELECT 总是看到同样的数据。

UPDATE, DELETE,和 SELECT FOR UPDATE 在搜索目标行上的行为和 SELECT 一样: 它们将只寻找在事务开始的时候已经提交的目标行。但是, 这样的目标行在被发现的时候可能已经被另外一个并发的事务更新了(或者是删除或者是标记为更新)。 在这种情况下,可串行化的事务将等待第一个正在更新的事务提交或者回滚 (如果它仍然在处理中)。如果第一个更新者回滚,那么它的影响将被忽略, 而这个可串行化的就可以继续更新它最初发现的行。但是如果第一个更新者 提交了(并且实际上更新或者删除了该行,而不只是为更新选中它) 那么可串行化事务将回滚,并返回下面信息。

ERROR: Can't serialize access due to concurrent update
因为一个可串行化的事务在 可串行化事务开始之后不能更改被其他事务更改过的行。

当应用收到这样的错误信息时,它应该退出当前的事务然后从头开始重新 进行整个事务.第二次运行时,该事务看到的前一次提交的修改是该数据库 初始的样子中的一部分,所以把新版本的行作为新事务更新的起点不会有 逻辑冲突.

请注意只有更新事务才需要重试 --- 只读事务从来没有串行化冲突.

可串行化事务级别提供了严格的保证:每个事务都看到一个完全一致的数据库 的视图.不过,如果并行更新令数据库不能维持串行执行的样子,那么应用 必须准备重试事务。因为重做复杂的事务的开销可能是非常可观的,所以我们 只建议在更新查询中包含足够复杂的逻辑,在读已提交级别中可能导致错误 的结果的情况下才使用. 最常见的是,可串行化模式只是在这样的情况下是必要的:一个事务连续做若干个查询, 而这几个查询必须看到数据库完全一样的视图。

不启动事务,可能会出现幻读(如果刚好在查询中别的查询改了数据),因为不启动事务的话就会使用墨认的事务级别,不过大部分情况我们是不用在意这个的。

许多正在使用 DB2 的人都认为对只读访问使用 COMMIT 是没有必要的。这些人认为,因为他们没有更改任何数据,所以没有必要使用 COMMIT。但是 COMMIT 不仅应用更改, 它还释放锁和声明(claim), 所以 COMMIT 会影响应用程序可用性。

我要解释在将 COMMIT 引入到进行只读访问的应用程序时要注意的可用性考虑事项以及声明和放弃(drain)的概念。我将使用的示例特定于只读环境;这些示例并不旨在讨论有关更新活动的 COMMIT 考虑事项。本文的范围限于在以下环境中对只读应用程序进行 COMMIT 操作:可能在该环境中执行进行更新的实用程序和其它应用程序。本文中,我将使用术语“应用程序可用性”来表示关于允许读访问的可用性。

获得声明

如果您熟悉 DB2,那么您可能对于对象(例如行、页面、表或表空间)上的不兼容锁(例如 X 和 S)会降低并发性有相当的了解。但如果您将只读程序与 ISOLATION UnCOMMITted Read(UR)联编在一起,会怎么样呢?这样做意味着您的只读程序可能不会获得任何锁。要牢记的是,如果涉及到大量删除,则与 ISOLATION UR 联编的程序确实会获得针对表或表空间的 MASS DELETE 锁。如果表空间使用工作文件数据库,则还会获得针对它的 IX 锁。但是,本文讨论只读方案。

所以问题是,在以 ISOLATION UR 隔离级别绑定进行只读访问的应用程序中使用 COMMIT 会对整个应用程序的可用性产生任何影响吗?

回答是:会产生影响。

尽管以 ISOLATION UR 隔离级别进行绑定时,进行只读访问的应用程序没有获得锁,但它确实声明了这个对象。而正如我将在本文稍后解释的,声明可能就是导致可用性降低的原因。正如我前面提到的,COMMIT 释放了声明。

现在,在应用程序可用性极其重要的环境中,当更新活动很少时,最好安排数据库维护作业(诸如 REORG 或 IMAGE COPIES)。这样做增加了 REORG 作业与长时间运行的只读批处理作业并发运行的可能性 — 这就是可用性成为问题的原因所在。

REORG 类似于其它实用程序,(在某个阶段)需要一个针对对象的 drain 锁,它会一直等待,直到能获得一个这样的锁为止。放弃是一种用于接管对象并序列化对它的访问的机制。当释放了对象上的所有声明,而且预先不存在 drain 锁时,就可以获得 drain 锁。Drain 请求器防止实用程序获得已放弃对象上的任何新声明。

从应用程序观点来看,您可能对可用性问题是如何结束的感到疑惑。REORG 作业将一直等待,直到应用程序的作业完成为止吗?如果在 REORG 实用程序可以请求放弃之前,应用程序获取了声明,则它会一直等待。但是如果 REORG 先请求放弃,那么它就不会等待。

当然,在启动 REORG 实用程序之前可以初始化应用程序的作业,以确保 REORG 会等待批处理作业。但是,如果还有另一个批处理作业或在线事务,那么它将会进入等待,因为它不能获取对象的声明。(新作业或事务必需获取声明,但是 Drain 请求器 REORG 会阻止该对象的任何声明。)

作为准备执行的只读程序和 REORG 实用程序(我将解释这两个程序可能会也可能不会同时执行)的结果所产生的应用程序非可用性程度取决于:

REORG 上指定的 SHRLEVEL OPTION
与 REORG 并发执行的批处理作业的 COMMIT 频率。

REORG 实用程序在终止之前,需要放弃所有声明类。如果 REORG 在执行时使用 SHRLEVEL REFERENCE 或 SHRLEVEL CHANGE,那么会在 SWITCH 阶段发生这个放弃,如果该实用程序执行时使用 SHRLEVEL NONE,那么会在 RELOAD 阶段发生这个放弃。

长时间运行的进程的 COMMIT 频率也会影响可用性。通过引入 COMMIT 就可以释放对象上的声明。注:如果批处理作业正在使用用 WITH HOLD 定义的游标,那么在经过 COMMIT 点之后仍会保留声明。在所有其它情况下,在 COMMIT 时都释放对象的声明。对象声明的持续时间并不取决于计划或包的 BIND 过程中指定的 RELEASE 参数(COMMIT 或 DEALLOCATE)。

表 1 演示了 REORG SHRLEVEL 参数和 COMMIT 频率对应用程序可用性的的影响。为简单起见,我们假设采用一个未分区的表空间。该表还假设批处理作业正在执行,并且在 T1 时间触发 REORG 作业。让我们研究该表中显示的各种情况。

表 1:比较使用 COMMIT 和不使用 COMMIT 时的进程

情况 1. 进程执行时不使用任何中间 COMMIT。使用 SHRLEVEL NONE 的 REORG 作业在 T1 时间启动。

情况 2. 进程执行时不使用任何中间 COMMIT。使用 SHRLEVEL REFERENCE 或 CHANGE 的 REORG 作业在 T1 时间启动。

情况 3. 进程执行时,在 T3 时间进行中间 COMMIT。使用 SHRLEVEL NONE 的 REORG 作业在 T1 时间启动。

情况 4. 进程执行时,在 T3 时间进行中间 COMMIT。使用 SHRLEVEL REFERENCE 或 CHANGE 的 REORG 作业在 T1 时间启动。

情况 5. 进程执行时,不包含任何中间 COMMIT,但与情况 2 相比,执行完成所花的时间比较长。

情况 6. 进程执行时,在 T3 时间进行中间 COMMIT,但与情况 4 相比,执行完成所花的时间比较长。

从表 1 您可以得出如下结论:

不进行中间 COMMIT 的批处理作业在与 REORG(SHRLEVEL NONE)作业并发执行时所产生的非可用性持续的时间最长。非可用性程度取决于进程(最可能的是批处理作业)的持续时间和正在被重组的表空间大小。
从情况 2 和情况 4 中可以看出,当 REORG 与不考虑 COMMIT 频率的批处理作业并发执行时,非可用性时间看起来相同;但是事实并非如此。如果情况 2 中的进程执行的时间更长,它会导致 REORG 等待的时间更长,从而增加应用程序非可用性时间。如果情况 4 中也出现类似的情况,它导致很少的进程等待时间(T5 时间),而 REORG 实用程序在切换(switch)阶段执行并在该阶段终止。但是,不会增加非可用性时间(请参阅情况 5 和情况 6)。
如果进程的等待时间超过了 DSNZPARM 中的 IRLMRWT 值,那么进程将以 -911(超时)终止。如果应用程序中这种情况很常见,那么请考虑提高 IRLMRWT 值,或将重试逻辑合并到程序中。
如果实用程序的等待时间超过了实用程序的超时时间,那么实用程序就超时。通过使 UTIMOUT 与 IRLMRWT 相乘可以确定实用程序超时时间。您可能有兴趣通过提高这两个参数中任一个或同时提高这两个参数来提高该实用程序的超时时间。但请谨慎使用这个方法。注:在所有情况中,每当 REORG 作业转至等待状态(等待获得 drain 锁),它会导致应用程序的非可用性。因此,当您在提高实用程序超时值时应该小心,因为它将对可用性产生直接影响。

解决方案

用 SHRLEVEL REFERENCE(允许并发的读操作)或用 SHRLEVEL CHANGE(允许并发的更改操作)方式执行 REORG 实用程序以及使长时间运行的进程发出中间 COMMIT 是解决可用性问题的优秀方案。决定 COMMIT 频率的关键因素是非可用性最大容许量以及 DSNZPARM 中定义的实用程序超时值。COMMIT 频率应该小于非可用性最大容许度。如果大于或等于的话,从本文中您已经了解到,它会导致可能的非可用性(当 REORG 在等待放弃所有声明时,禁止任何新的声明)。

即使 COMMIT 频率低于非可用性最大容许度,但使用 DB2 V6(或更低版本)时,已分区表空间的 SWITCH 阶段也可能会花相当一段时间(以便 IDCAMS 进行重命名),从而导致非可用性。使用 DB2 V7,通过使用 FASTSWITCH YES(缺省值)会缓解该问题。如果 COMMIT 频率大于实用程序超时值,那么它就可能使 REORG 实用程序未完成任何工作就超时了。DB2 V7 引入了两个新选项:DRAIN WAIT(指定覆盖实用程序超时值的时间)和 RETRY(在发出超时之前进行重试的次数),以防止 REORG 超时。

做就是了

虽然在引入中间 COMMIT 时会产生与之相关的额外开销(至少需要一个额外的数据库调用),但是通过仔细规划,用相当低的投资(CPU 开销)获得相当好的回报(应用程序可用性)是可能的。

后来在http://www.javaeye.com/topic/8850?page=1看到robbin对此问题的重复:

如果只提交一个查询,有必要用事务吗?这个问题之前已经讨论过
http://forum.javaeye.com/viewtopic.php?t=1603
但是并没有得出明确的结论。先让我们看看事务的定义:

引用

Transactions are described in terms of ACID properties, which are as follows:
n Atomic: all changes to the database made in a transaction are rolled back if any
change fails.
n Consistent: the effects of a transaction take the database from one consistent
state to another consistent state.
n Isolated: the intermediate steps in a transaction are not visible to other users of
the database.
n Durable: when a transaction is completed (committed or rolled back), its effects
persist in the database.

即ACID的定义,从上面看来,似乎除了isolated之外,和只读查询都没有关系。那么是否只读查询不需要事务呢?

再看看Oracle对于只读事务的定义:
引用
Read-Only Transactions
By default, Oracle guarantees statement-level read consistency. The set of data returned by a single query is consistent with respect to a single point in time. However, in some situations, you might also require transaction-level read consistency. This is the ability to run multiple queries within a single transaction, all of which are read-consistent with respect to the same point in time, so that queries in this transaction do not see the effects of intervening committed transactions.

If you want to run a number of queries against multiple tables and if you are not doing any updating, you prefer a read-only transaction. After indicating that your transaction is read-only, you can run as many queries as you like against any table, knowing that the results of each query are consistent with respect to the same point in time.

Oracle默认情况下保证了SQL语句级别的读一致性,即在该条SQL语句执行期间,它只会看到执行前点的数据状态,而不会看到执行期间数据被其他SQL改变的状态。

而Oracle的只读查询(read-only transaction)则保证了事务级别的读一致性,即在该事务范围内执行的多条SQL都只会看到执行前点的数据状态,而不会看到事务期间的任何被其他SQL改变的状态。

因此我们可以得出结论:

如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。

只读事务与读写事务区别

对于只读查询,可以指定事务类型为readonly,即只读事务。由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段,例如Oracle对于只读事务,不启动回滚段,不记录回滚log。

在JDBC中,指定只读事务的办法为:
connection.setReadOnly(true);

在Hibernate中,指定只读事务的办法为:
session.setFlushMode(FlushMode.NEVER);
此时,Hibernate也会为只读事务提供Session方面的一些优化手段

在Spring的Hibernate封装中,指定只读事务的办法为:
bean配置文件中,prop属性增加“readOnly”
其实事情都有两面性,最后怎么抉择要看实际需要,还有个人爱好……

参考:
http://www.javaeye.com/topic/8850?page=1 http://www.javaeye.com/topic/1603?page=2 http://msdn.microsoft.com/zh-cn/library/ms175523.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐