数据库事务需要满足四个特性,ACID,即原子性(Atomic),一致性(Consistency),隔离性(Isolation),持久性(Durability)。在关系数据库中,这些特性需要借助借助redo log、锁、snapshot等手段来实现。
隔离性要求各事务之间相互独立,所进行的操作不会相互影响,其用意还是保证各数据读写操作的一致性。标准SQL92规范定义了四种隔离级别,以及相应要禁止的三种现象(Phenomena)。隔离级别越高,数据一致性的保证越强,但同时数据库的并发能力也越差。
数据库并发操作引起的问题(现象)有:
1. 脏读(Dirty read)。事务A读取了事务B中尚未提交的数据。如果事务B回滚,则A读取使用了错误的数据。
2. 不可重复度(Unrepeatable read)。在事务A多次的读取过程中,事务B对数据进行了修改,导致事务A多次读取的数据不一致。
3. 幻读(Phantom read)。在事务A多次读取构成中,事务B对数据进行了新增操作,导致事务A多次读取的数据不一致。幻读和不可重复读的区别在于,不可重复是针对记录的update操作,只要在记录上加写锁,就可避免;幻读是对记录的insert操作,要禁止幻读必须加上全局的写锁(比如在表上加写锁)。
另外说一下两类丢失更新:
4.第一类丢失更新(回滚丢失,Lost update)。在事务A期间,事务B对数据进行了更新;在事务A撤销之后,覆盖了事务B已经提交的数据。SQL92没有定义这种现象,标准定义的所有隔离界别都不允许第一类丢失更新发生。
5.第二类丢失更新(覆盖丢失,Second lost update)。在事务A期间,事务B对数据进行了更新;在事务A提交之后,覆盖了事务B已经提交的数据。第二类丢失更新,实际上和不可重复读是同一种问题。
SQL92定义的四种隔离级别:
1.未提交读(Read uncommitted)。写操作加写锁,读操作不加锁。禁止第一类丢失更新,但是会出现所有其他数据并发问题。
2.提交读(Read committed)。写操作加写锁,读操作加读锁。禁止第一类丢失更新和脏读。这是大部分关系数据库的默认隔离级别。
3.可重复读(Read repeatable)。对于读操作加读锁到事务结束,其他事务的更新操作只能等到事务结束之后进行。和提交读的区别在于,提交读的读操作是加读锁到本次读操作结束,可重复读的锁粒度更大。禁止两类丢失更新,禁止脏读和不可重复度,但是可能出现幻读。
4.序列化(Serializable)。读操作加表级读锁至事务结束。可以禁止幻读。
第一类丢失更新 | 脏读 | 不可重复读 | 第二类丢失更新 | 幻读 | |
Read uncommitted | 禁止 | 不禁止 | 不禁止 | 不禁止 | 不禁止 |
Read committed | 禁止 | 禁止 | 不禁止 | 不禁止 | 不禁止 |
Read repeatable | 禁止 | 禁止 | 禁止 | 禁止 | 不禁止 |
Serializable | 禁止 | 禁止 | 禁止 | 禁止 | 禁止 |
大多数关系数据库默认使用Read committed的隔离级别,Mysql InnoDB默认使用Read repeatable的隔离级别,这和Mysql replication机制使用Statement日志格式有关。各数据库隔离级别的实现也是有差别的,例如Oracle支持Read committed和Serializable两种隔离级别,另外可以通过使用读快照在Read committed级别上禁止不可重复读问题;Mysql InnoDB在Read repeatable级别上使用next-key locking策略来避免幻读现象的产生。