一项事务是由一条或是多条表达式所组成的一个不可分割的工作单元。我们通过提
交commit()或是回退rollback()来结束事务的操作。
在JDBC中,事务操作默认是自动提交。也就是说,一条对数据库的更新表达式代表
一项事务操作。操作成功后,系统将自动调用commit()来提交,否则将调用rollback()
来回退。
其次,在JDBC中,可以通过调用setAutoCommit(false)来禁止自动提交。之后就可
以把多个数据库操作的表达式作为一个事务,在操作完成后调用commit()来进行整体提
交。倘若其中一个表达式操作失败,都不会执行到commit(),并且将产生响应的异常。
此时就可以在异常捕获时调用rollback()进行回退。这样做可以保持多次更新操作后,
相关数据的一致性。
jdbc api提供了5种数据库事务隔离操作:
static int TRANSACTION_NONE = 0;
static int TRANSACTION_READ_UNCOMMITTED = 1;
static int TRANSACTION_READ_COMMITTED = 2;
static int TRANSACTION_REPEATABLE_READ = 4;
static int TRANSACTION_SERIALIZABLE = 8;
下面介绍数据库中发生数据不一致的四个概念:
“脏”数据读写(Dirty Reads):当一个事务修改了某一数据行的值而未提交时,另一事
务读取了此行值。倘若前一事务发生了回退,则后一事务将得到一个无效的值(“脏”数
据)。(这个会发生的条件一直没明白,一个事务未提交的话,另一个事务只能取到原来
的值,不可能取到更新过的未提交的数据(就算事务自动提交,那也是提交了,也不可能
回滚了啊,除非回滚到commit之前的savepoint,那是有可能的)。有谁知道,请麻烦告
诉我一下,发生脏数据读写的前提条件?)
不可重复读写(non-Repeatable Reads):当一个事务在读取某一数据行时,另一事务同
时在修改此数据行,并进行了commit。则前一事务在重复读取此行时将得到一个不一致的
值。
错误(映像)读写(Phantom Reads):当一个事务在某一表中进行数据查询时,另一事务恰
好插入了满足了查询条件的数据行。则前一事务在重复读取满足条件的值时,将得到一
个额外的“影像”值。
丢失更新:有两个事务都对同一条记录进行了读取,然后第一个事务进行修改了先进行
commit,第二个事务后修改进行commit,这样前一个事务的更新就丢失了。(这个最难解决的)
用jdbc进行设置事务隔离级别的时候,要注意,数据库及其驱动程序必须得支持相应的
事务操作操作才行。我在对oracle数据库进行操作的时候,发现
TRANSACTION_REPEATABLE_READ,TRANSACTION_READ_UNCOMMITTED这2个事务无效,不知
道如何把它们打开?
jdbc提供了2个接口函数来设置事务隔离级别和获取当前的事务隔离级别:
setTransactionIsolation
getTransactionIsolation
上述5个事务隔离级别随着值的增加,其事务的独立性增加,更能有效地防止事务操作之
间的冲突,同时也增加了加锁的开销,降低了用户之间访问数据库的并发性,程序的运
行效率也会随之降低。因此得平衡程序运行效率和数据一致性之间的冲突。
一般来说,对于只涉及到数据库的查询操作时,可以采TRANSACTION_READ_UNCOMMITTED
方式;对于防止脏读(有待明确脏读的确切发生条件,概念),可以采用
TRANSACTION_READ_COMMITTED方式;对于防止脏读和不可重复读,可以采用
TRANSACTION_REPEATABLE_READ;在数据一致性要求更的场合再考虑最后一项,由于涉及
到表加锁(没验证过),因此会对程序运行效率产生较大的影响。
对于最后一个事务隔离级别:TRANSACTION_SERIALIZABLE。我的试验结果是,事务之间
是串行执行的,除了select语句。这样上面的4种不一致都可以避免,但是这个级别的开
销很大,具体多大我没试验。
事务1 事务2
select
select
update
update(先等着事务1进行提交才能运行)
commit
抛8177错误(事务1commit后运行update失败,就是说事务有交叉,不
允许更新)
但是如果事务2仅有读取操作,那是可以交叉的,不影响数据的一致性。
另外,在Oracle中数据库驱动对事务处理的默认值是TRANSACTION_NONE,即不支持事务
操作,所以需要在程序中手动进行设置。
最后,本人觉得之前提到的4种数据不一致的问题,在数据库并发中是必然存在的,解决
前3种用一般的事务隔离理论上是可以的,但是要解决丢失更新,事务隔离必须要到
TRANSACTION_SERIALIZABLE,这个损失的效率有待进一步去确认。另外还有2种解决办法
,当要对读取的数据可能进行更新修改时,都一律用select...for update。但这样的话
又容易出现死锁(事务1对表a进行了for update之后要对表b操作for update,而事务2对
表b进行了for update,要对表a进行for update,但都没有进行事务提交,都还是锁定
状态,所以就产生了死锁);读取时只用select,当要更新时,在where条件里把select
到的值也添加进去,进行校验,这个是相对来说比较妥当的解决办法。
目前我对于数据库锁的概念理解还不是很深刻,还在积累经验,学习中。要是自己能手
工加入锁,可以锁住后不让其他事务进行select的就好了?