# ACID以及事务隔离级别
ACID是指在可靠数据库管理系统(DBMS)中,事务(Transaction)所应该具有的四个特性
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
这是可靠数据库所应具备的几个特性,这些特性保证了数据库事物的可靠
在数据库中,对数据的一系列操作在逻辑上可以看成一个整体的操作,这个整体的操作就叫事物
# 原子性(Atomicity)
原子性是指事务是一个不可再分割的工作单位,要求每个事物中的所有操作要么全部完成,要么就像全部没有发生一样
如果事物中的部分操作失败了,则整个事物事物失败了,结果就是数据库中的状态保持没变。原子性系统必须保证在各种情况下的原子性,包括主机断电、主机发生了错误、主机奔溃。对外界来说,一个提交了的事物看起来(通过事物对数据库产生的影响)是不可分的,一个失败了的事物,对外界来说就好像什么都没有发生过一样
# 一致性(Consistency)
一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性
一致性确保了任何事物都会使数据库从一种合法的状态变为另一种合法的状态。通过定义的各种规则,包括约束(Constraints)、级联(Cascades)、触发器(Triggers)以及它们的组合来保证写入数据库的所有数据都必须是合法的。一致性并不能保证事物(程序)的正确性,换句话说事物的一致性并不一定如程序员所期望的那样(这应该是由应用层代码来负责的),它只能保证数据库中的所有数据都不会违反定义好的规则,不管程序有没有发生错误甚至是发生了任何错误都不会违反定义好的规则
# 隔离性(Isolation)
隔离性保证了并发执行多个事物对系统的状态的影响和串行化执行多个事物对系统的状态的影响是一样的
多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果
这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据
数据库管理系统采用锁机制来实现事务的隔离性。当多个事务同时更新数据库中相同的数据时,只允许持有锁的事务能更新该数据,其他事务必须等待,直到前一个事务释放了锁,其他事务才有机会更新该数据
# 不考虑事务的隔离性
如果不考虑事务的隔离性,那么会发生下面所示的问题
- 脏读(Dirty Read)
所谓脏读是指一个事务中访问到了另外一个事务未提交的数据
当一个事务读取另一个事务尚未提交的修改时,产生脏读
- 丢失更新(Lost Update)
当系统允许两个事务同时更新同一数据时,后一个事务拿到了前一个事务的中间状态数据(一个事务的更新操作被另外一个事务的更新操作所覆盖),一个事务的更新覆盖了其它事务的更新结果导致丢失更新
- 第一类丢失更新,回滚丢失,隔离级别可以解决
- 第二类丢失更新,覆盖丢失/两次更新问题,只能通过锁解决,乐观锁或悲观锁
- 不可重复读(NonRepeatable Read)
同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生不可重复读
通俗解释是指在同一个事务内根据同一个条件对行记录(同一条数据)进行多次查询,但是搜出来的结果(数据)却不一致,发生不可重复读的原因是在多次搜索期间查询条件覆盖的数据被其他事务修改了
- 幻读(Phantom Read)
同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻像读
通俗解释是指同一个事务内多次查询返回的结果集不一样(比如增加了或者减少了行记录)
幻读和不可重复读的区别,幻读和不可重读读都是读取了另一个事务中已经提交的数据,不同的是不可重复读多次查询的都是同一个数据项,针对的是对同一行数据进行修改(Update)或删除(Delete),而幻读针对的是一个数据整体(数据的条数),主要是插入(Insert)操作
比如,同一个事务A内第一次查询时候有N条记录,但是第二次同等条件下查询却又N+1条记录,这就好像产生了幻觉,为啥两次结果不一样。其实和不可重复读一样,发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据。不同在于不可重复读是同一个记录的数据内容被修改了,幻读是数据行记录变多了或者少了
# Read Uncommitted(未提交读)
读取未提交内容,在该隔离级别,所有事物都可以看到其它未提交事务的执行结果,即在未提交读级别,事务中的修改,即使没有提交,对其他事务也都是可见的,该隔离级别很少用于实际应用。读取未提交的数据,也被称之为脏读(Dirty Read)。该隔离级别最低,并发性能高
# Read Committed(提交读)
读取提交内容,这是大多数数据库默认的隔离级别。它满足了隔离的简单定义,一个事务只能看见已经提交事务所做的改变。换句话说,一个事务从开始到提交之前,所做的任何修改对其他事务都是不可见的
# Repeatable Read(可重复读)
可重复读可以确保同一个事务,在多次读取同样数据的时候,得到同样的结果。可重复读解决了脏读的问题,不过理论上,这会导致另外一个棘手的问题,幻读(Phantom Read)。MySQL中的InnoDB和Falcon存储引擎通过MVCC(Multi-Version Concurrency Control,多版本并发控制)机制解决了该问题,需要注意的是,多版本控制只是解决了不可重复读的问题,而加上间隙锁(也就是它这里所谓的并发控制)才解决了幻读问题
# Serializable(可串行化)
序列化,这是最高的隔离级别,它通过强制事务排序,强制事务串行执行,从而解决幻读问题,简而言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的等待超时和锁竞争,实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑使用这个隔离级别,这是花费代价最高但是最可靠的隔离级别
# 隔离级别总结(Isolation Level)
隔离级别(Isolation Level)与并发性是互为矛盾的
隔离级别 | Read Uncommitted(未提交读) | Read Committed(提交读) | Repeatable Read(可重复读) | Serializable(可串行化) |
---|---|---|---|---|
脏读(Dirty Read) | Y | - | - | - |
丢失更新(Lost Update) | Y | Y | - | - |
不可重复读(NonRepeatable Read) | Y | Y | - | - |
幻读(Phantom Read) | Y | Y | Y | - |
默认级别数据库 | - | Oracle,SQL Server | MySQL(InnoDB) | - |
并发性能 | 最高 | 第二 | 第三 | 最低 |
隔离程度越高,数据库的并发性越差;隔离程度越低,数据库的并发性越好
# 持久性(Durability)
持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚
即使出现了任何事故比如断电(甚至事物刚被提交数据库系统就发生了奔溃)等,事务一旦提交,则持久化保存在数据库中
# 最后
好多数据库依赖锁来实现ACID能力。锁意味着事物在其需要访问的数据上打个标记,这样一来数据库管理系统就会知道这些数据在该事物完成(事物成功或失败)之前不允许其他事物修改这些被打了标记的数据。锁在数据被处理之前必须获取到,也包括处理那些只会被读取但不会被修改的数据之前也要获取锁。非平常事物通常需要大量锁,导致了不小的性能开销同时也阻塞了其他事物。例如,用户A正在执行一个事物,需要读取某一行数据而这时另外一个用户B正在修改这一行数据。用户B必须等到用户A的事物彻底完成。通常可以通过两个阶段锁来保证全隔离性
- 美团-Innodb中的事务隔离级别和锁的关系: https://tech.meituan.com/2014/08/20/innodb-lock.html (opens new window)
- 感谢敦格的谈谈数据库的ACID: https://blog.csdn.net/shuaihj/article/details/14163713 (opens new window)
- 感谢一中晴哥威武的数据库的ACID属性: https://www.cnblogs.com/liuqing576598117/p/9564916.html (opens new window)
- 感谢lhrbest的事务的4种隔离级别(Isolation Level)分别是什么: http://blog.itpub.net/26736162/viewspace-2638951 (opens new window)
- 感谢Jade_K的事务隔离级别(IsolationLevel): https://www.cnblogs.com/wms01/p/6183241.html (opens new window)
- 感谢阿里加多的何为脏读、不可重复读、幻读: https://www.jianshu.com/p/f7ac1b22e899 (opens new window)