所谓幻读是事务的隔离性里面的概念,Mysql中的Innodb引擎是支持事务的,所以本章讲解Innodb的事务特性之一隔离性中的幻读是如何被解决的。
前提知识:
事务的四大特性:ACID
A:Atomic(原子性):一系列操作构成,要么都执行,要么都不执行
C:Consistency(一致性):数据更改前即更改后都是符合业务逻辑的,不会出现脏数据。
I:Isolation(隔离性):读事务和写事务在时间轴上有交叉时,写事务导致的数据的修改对读事务的可见性上的一种隔离。
D:Durability(持久性):数据更改之后会持久化。
没有隔离性产生的问题:
脏读:A事务读取了B事务未提交的数据
不可重复读:A事务读取同一行数据,多次读取结果不同
幻读:A事务读取到了B事务插入的数据
隔离性等级:
隔离性分为四个等级:未提交读、已提交读、可重复读、可序列化。随着隔离等级的提高,读取的数据一致性越高,但并发性越低。
脏读 | 不可重复读 | 幻读 | |
未提交读 | 未解决 | 未解决 | 未解决 |
已提交读 | 解决 | 未解决 | 未解决 |
可重复读(默认) | 解决 | 解决 | 未解决 |
可序列化 | 解决 | 解决 | 解决 |
问题缘由:
Mysql作为数据库,保存了一份数据,每时每刻都会接收很多请求进来,在如何保证数据安全的基础上提高并发性,就是一直在追求的目标。在时间轴上,如果是读请求与读请求有交叉,那么是没有问题的,不会出现数据同时被修改的可能,并发理论上无上限;写请求与写请求有交叉不在本笔记讨论范围内;如果是读请求与写请求在时间轴上有交叉,虽然不会出现数据不安全的可能,但是对于读请求失去了一个重要的特点:不能保证一个事务中读请求前后的一致性,也就是说不能保证在一个事务中第一次读和第二次读到的是相同的数据。因为这个原因,所以才有了所谓的隔离性!
从上面的表格中,虽然可序列化解决了所有问题,可序列化其实就相当于synchronized,把所有的请求进行同步,大大降低了Mysql的并发性,实际上没人会采用。我们的目标就是保证数据安全、数据读取一致的前提下,尽量提高并发性。Mysql默认的数据隔离级别是可重复读,但可重复读本身并没有解决幻读。
Innodb解决幻读的方式:MVCC(多版本并发控制)
其实这里就是讲一个概念模型,我相信这个概念模型可以让你看清MVCC:
Innodb的表实际上会有两个隐藏列:新增版本号、删除版本号。新增版本号记录的是这条数据行新增时记录的系统版本号,删除版本号记录的是这条数据被删除时记录的系统版本号。注意:每次获取系统版本号,都是获取一个全局唯一且递增的一个新的系统版本号。这就和HBase有点类似,在原来的数据的定位维度上加了一个:时间维度。
Innodb进行一些操作时,会颁发系统版本号:在事务开始时,事务获取一个系统版本号,以方便在事务内部使用;在数据行被新增或删除时,获取版本号记录在版本号列中。
写事务:事务结束时,根据写的类型,如果是INSERT,那么将获取一个系统版本号放在对应数据行的新增版本号列中;如果是DELETE,那么将获取一个系统版本号放在对应的数据行的删除版本号列中;如果是UPDATE,那么会获取一个系统版本号记录在对应的旧数据行的删除版本号中,同时获取一个系统版本号记录在添加的新的数据行的新增版本号列中。(是的,你没有看错,UPDATE的时候,同一主键暂时存在两条数据!)
读事务:事务开始时获取一个系统版本号,读取数据时,如果行的新增版本号 < 事务版本号 < 行的删除版本号,那么才可以读取。
MVCC只在提交读、可重复读两个隔离级别上工作。可以对照上图时间轴,思考一下,不可重复读、幻读的解决过程。
总结:Mysql的Innodb存储引擎就是通过MVCC的方式,解决了不可重复读、幻读的问题,保证了隔离性,可以在不加锁(保证并发性)的情况下,保证读取数据的一致性。