读写异常
并发操作带来的数据不一致性包括丢失修改(lost update)、不可重复读(non-repeatable read)和读“脏”数据(dirty read)。
丢失修改
两个事务 \(T_1\) 和 \(T_2\) 读入同一数据并修改,\(T_2\) 的提交结果破坏了 \(T_1\) 的提交结果,导致 \(T_1\) 的修改被丢失。
不可重复读
包括三种情况:
- \(T_1\) 读取某一数据后,再次读取该数据时,得到不一样的值。
- \(T_1\) 读取某一数据后,再次读取该数据时,该数据已经被删除。
- \(T_1\) 读取某一数据后,再次读取该数据时,多了一些记录。
后两种不可重复读也成为幻读(phantom row)现象。
读“脏”数据
\(T_1\) 修改某数据写入磁盘后,\(T_2\) 读取该数据,\(T_1\) 由于某种原因被撤销,数据库中该数据恢复原值,\(T_2\) 读到的数据与数据库中不一致。
隔离级别
ANSI SQL标准定义了 4 种事务隔离级别来避免 3 种数据不一致的问题。事务等级从高到低,分别为:序列化(Serializable)、可重复读(Repeatable Read)、已提交读(Read Committed)和未提交读(Read Uncommitted)。多版本并发控制(MVCC)带来的快照隔离(Snapshot Isolation)为传统的隔离级别添加了一个添加了一个灵活选项。
分布式数据库打破了传统数据库的一致性原则,线性化(Linearizability)则是要解决分布式系统中多副本的一致性读写问题。
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
序列化 | 不允许 | 不允许 | 不允许 |
可重复读 | 不允许 | 不允许 | 允许 |
已提交读 | 不允许 | 允许 | 允许 |
未提交读 | 允许 | 允许 | 允许 |
Serializable
串行化(Serializability,SER)解决的是数据库中多个事务的隔离性问题。在一个事务内,针对同一个对象读到的永远是同一个值,哪怕其它事务已经提交了新值。所以显然,有可能读到的是旧值。
在不同的事务之间,互相不干扰。每个事务都认为自己就是唯一的事务。实际上是多个事务并发在执行。可串行化核心是要保证并发执行的多个事务最终的效果与按某个特性顺序串行化地执行这些事务的效果一致。但至于与什么特定顺序一致, 则不保证。例如,有三个事务 \(T_1\),\(T_2\) 和 \(T_3\) 并发在执行。如果串行化顺序地执行这三个事务,则可能的组合有 6 种。串行化只会保证这三个事务并发执行的最终结果肯定与其中一种组合的结果一致,但具体是哪种组合,则不保证。这种跨事务的正确性,则需要应用层自己来解决。
从数据库的角度来看,能做到串行化的隔离级别,已经是级别非常高的要求了。本质上其解决的是数据的正确性的问题,保证不会由于多个事务并发执行,而导致出现不正确的数据。
解决串行化的核心技术包括两阶段锁(2PL)及多版本并发控制(MVCC)。
Repeatable Read
一个事务一旦开始,事务过程中所读取的所有数据不允许被其他事务修改。该隔离级别不可避免“幻读”。
因为它只“保护”了它读取的数据不被修改,但是其他数据会被修改。如果其他数据被修改后恰好满足了当前事务的过滤条件(where 语句),那么就会发生“幻影读”的情况。
Read Committed
一个事务能读取到其他事务提交过的数据。一个事务在处理过程中如果重复读取某一个数据,而且这个数据恰好被其他事务修改并提交了,那么当前重复读取数据的事务就会出现同一个数据前后不同的情况。
在这个隔离级别会发生“不可重复读”。
Read Uncommitted
一个事务能读取到其他事务修改过,但是还没有提交的(Uncommitted)数据。
数据被其他事务修改过,但还没有提交,就存在着回滚的可能性,这时候读取这些“未提交”数据的情况就是“脏读”。
在这个隔离级别会发生“脏读”。
并发控制
乐观与悲观
乐观控制使用的场景是并行事务不太多的情况,只需要很少的时间来解决冲突。冲突检查有多种实现模式,最常用的是多版本控制。
悲观控制最常见的是锁控制。
多版本
多版本并发控制是一种实现乐观控制的经典模式。
给每行数据设置一个版本号,并使用单调递增的版本号生成器来产生这些版本号。从而保证每条记录的版本是唯一的。同时给每个事务分配一个 ID 或时间戳,从而保证读取操作可以读到事务提交之前的旧的数据。
MVCC 需要区分提交版本与未提交版本两个部分的数据,最近一次提交版本被认为是当前版本,从而可以被所有的事务读取。根据隔离级别的不同,读取操作可能或不可能读到这个未提交的数据。
事务提交时,实际已经写入完成。
冲突检测机制:前向检测(froward)是指在事务内进行读取操作时就进行检测;后向检测(backward)是在提交时进行检测。