事务隔离级别

更新遗失(Lost update)

两个事务都同时更新一行数据,但是第二个事务却中途失败退出,导致对数据的两个修改都失效了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。

基本上就是指某个事务对字段进行更新的信息,因另一个事务的介入而遗失更新效力。举例来说,若某个字段数据原为 ZZZ,用户 A、B 分别在不同的时间点对同一字段进行更新事务,如下图:

jdbc-trans1.png

单就用户 A 的事务而言,最后字段应该是 OOO,单就用户 B 的事务而言,最后字段应该是 ZZZ。在完全没有隔离两者事务的情况下,由于用户 B 撤销操作时间在用户 A 确认之后,因此最后字段结果会 是ZZZ,用户A看不到他新确认 的OOO 结果,用 户A 发生更新遗失问题。

如果要避免更新遗失问题,需要设置隔离级别为“可读取未确认”(Read Uncommitted),如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。

该隔离级别可通过“排他写锁”实现。JDBC 可通过 Connection 的 setTransactionIsolation() 设置为 TRANSACTION_UNCOMMITTED 来提示数据库指定此隔离行为。

实现后如下图:

jdbc-trans2.png

提示数据库“可读取未确认”的隔离层次之后,数据库至少得保证事务能避免更新遗失问题,通常这也是具备事务功能的数据库引擎会采取的最低隔离层级。不过这个隔离层级读取错误数据的机率太高,一般默认不会采用这种隔离级别。

脏读(Dirty read)

两个事务同时进行,其中一个事务更新数据但未确认,另一个事务就读取数据,就有可能发生脏读问题,也就是读到所谓脏数据、不干净、不正确的数据。如下图:

jdbc-trans3.jpg

如果要避免脏读问题,可以设置隔离层级为“可读取确认”(Read committed)。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。也就是事务读取的数据必须是其他事务已确认的数据。JDBC 可通过 Connection 的 setTransactionIsolation() 设置为 TRANSACTION_COMMITTED 来提示数据库指定此隔离行为。

实现后如下图所示:

jdbc-trans4.jpg

提示数据库“可读取确认”的隔离层次之后,数据库至少得保证事务能避免脏读与更新遗失问题。

无法重复的读取(Unrepeatable read)

某个事务两次读取同一字段的数据并不一致。如下图:

jdbc-trans5.jpg

如果要避免无法重复的读取问题, 可以设置隔离层级为“可重复读取”(Repeatable read),这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。也就是同一事务内两次读取的数据必须相同。JDBC 可通过 Connection 的 setTransactionIsolation() 设置为 TRANSACTION_REPEATABLE_READ 来提示数据库指定此隔离行为。

实现后如下图所示:

jdbc-trans6.jpg

在数据库上这个做法影响性能较大,另一个基本做法是事务正在读取但尚未确认前,另一事务会在暂存表格上更新。提示数据库“可重复读取”的隔离层次之后,数据库至少得保证事务能避免无法重复读取、脏读与更新遗失问题。

幻读(Phantom read)

同一事务期间,读取到的数据笔数不一致。例如,事务 A 第一次读取得到五笔数据,此时事务 B 新增了一笔数据,导致事务 B 再次读取得到六笔数据。

如果隔离行为设置为可重复读取,但发生幻读现象,可以设置隔离层级为“可循序”(Serializable),它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。也就是在有事务时若有数据不一致的疑虑,事务必须可以按照顺序逐一进行。JDBC 可通过 Connection 的 setTransactionIsolation() 设置为 TRANSACTION_SERIALIZABLE 来提示数据库指定此隔离行为。

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为 Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

隔离行为 更新遗失 脏读 无法重复读取 幻读
可读取未确认 Read Uncommitted 预防
可读取确认 Read Uncommitted 预防 预防
可重复读取 Repeatable read 预防 预防 预防
可循序 Serializable 预防 预防 预防

数据库事务的 ACID 特性:

  1. 原子性(Atomicity)

整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

例如:银行刷卡业务,当消费的时候 A 顾客的卡钱减少,然后 B 商城的账户加钱,只有当这 2 个操作都成功的时候才可以进行提交,否则都必须回滚。这就是事务的原子性,因为这 2 个操作不可以分开。

  1. 一致性(Consistency)

在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

例如:有表 A 和表 B,表 A 中的一个字段 aa 是表 B 中 bb 字段的外键,并且 bb 字段是必填的,当在 B 表中插入数据的时候如果 bb 字段的值在 A 表中没有,则事务必须回滚否则会破坏数据库的完整性约束。

  1. 隔离性(Isolation)

两个事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据。

例如:A 顾客银行卡刷的钱与 B 顾客互相不影响。

  1. 持久性(Durability)

在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

例如:在A顾客消费成功后,银行卡的钱不可以被回滚。

—EOF—