事务ACID特性与隔离级别

2,460 阅读7分钟
事务指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。

ACID

从业务角度出发,对数据库的一组操作要求保持4个特征:

Atomicity(原子性):

一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。

Consistency(一致性):

数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。
数据库总是从一个一致性状态转换到另一个一致状态。

Isolation(隔离性):

通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。注意这里的“通常来说”,后面的事务隔离级级别会说到。

Durability(持久性):

一旦事务提交,则其所做的修改就会永久保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。使用重做日志来保证持久性。(持久性的安全性与刷新日志级别也存在一定关系,不同的级别对应不同的数据安全级别。)

事务的 ACID 特性概念简单,但不是很好理解,主要是因为这几个特性不是一种平级关系:
  • 只有满足一致性,事务的执行结果才是正确的。
  • 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。
  • 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。
  • 事务满足持久化是为了能应对数据库崩溃的情况。

AUTOCOMMIT

MySQL 默认采用自动提交模式。也就是说,如果不显式使用START TRANSACTION语句来开始一个事务,那么每个查询都会被当做一个事务自动提交。

并发一致性问题

在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。

1. 更新丢失(Lost Update):

定义:当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题,最后的更新覆盖了由其他事务所做的更新。

例如:
两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。 最后保存其更改副本的编辑人员覆盖另一个编辑人员所做的更改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题。
T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。


2. 脏读(Dirty Reads):

定义:一个事务正在对一条记录做修改,在这个事务完成并提交前, 这条记录的数据就处于不一致状态; 这时, 另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做"脏读"。

T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。


3. 不可重复读(Non-Repeatable Reads):

定义:一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读” 。

T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。


4. 幻读(Phantom Reads): 

定义:一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读” 。

T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和第一次读取的结果不同。


幻读和不可重复读的区别:

不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(因为中间有其他事务提交了修改)
幻读的重点在于新增或者删除:在同一事务中,同样的条件,,第一次和第二次读出来的记录数不一样。(因为中间有其他事务提交了插入/删除)

不可重复读和脏读的区别:

脏读是读到未提交的数据,而不可重复读读到的却是已经提交的数据。

隔离级别

SQL标准定义了4类隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。低级别的隔离级别一般支持更高的并发处理,并拥有更低的系统开销。

第1级别:Read Uncommitted(未提交读)

所有事务都可以看到其他未提交事务的执行结果

本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少
该级别引发的问题是——脏读(Dirty Read):读取到了未提交的数据

第2级别:Read Committed(提交读)

这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)
它满足了隔离的简单定义:

一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。

这种隔离级别出现的问题是——不可重复读(Nonrepeatable Read):不可重复读意味着我们在同一个事务中执行完全相同的select语句时可能看到不一样的结果。导致这种情况的原因可能有:

有一个交叉的事务有新的commit,导致了数据的改变;

一个数据库被多个实例操作时,同一事务的其他实例在该实例处理期间可能会有新的commit

第3级别:Repeatable Read(可重复读)

这是MySQL的默认事务隔离级别
保证在同一个事务中多次读取同样数据的结果是一样的。
它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行

此级别可能出现的问题——幻读(Phantom Read):当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行
InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决幻读问题;InnoDB还通过间隙锁解决幻读问题

MVCC 并不能解决幻读的问题,Next-Key Locks 就是为了解决这个问题而存在的。在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题。

第4级别:Serializable(可串行化)

这是最高的隔离级别,强制事务串行执行。

它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。
在这个级别,可能导致大量的超时现象和锁竞争


小结

这篇文章给大家介绍了数据库事务定义的ACID特性和隔离级别,以及在不同的隔离级别下分别会产生哪些并发一致性问题,更新丢失、脏读、不可重复读、幻读都是我们要避免的,所幸MySQL的默认隔离级别为可重复读,其使用MVCC+next-key locks避免了幻读问题。其中的原理我将在下篇文章中给大家介绍。


参考自

zhuanlan.zhihu.com/p/29166694

github.com/CyC2018/CS-…