MySQL 事务的隔离级别与 MVCC
事务并发执行会遇到的问题
- 脏写:一个事务修改了另一个未提交事务修改过的数据。这样会导致其中一个事务明明进行了修改,但最终却发现并没有修改
- 脏读:一个事务读到了另一个未提交事务修改过的数据
- 不可重复读:一个事务每次读一个记录时,都能读到其他已提交事务对这条记录的最新的修改。也就是虽然该事务读到的都是已提交事务做的修改,但在同一个事务内,每次读同一条记录的结果可能是不同的
- 幻读:当同一查询多次执行时,由于其它事务在这个数据范围内执行了插入操作,会导致每次返回不同的结果集(和不可重复读的区别:针对的是一个数据整体/范围;并且针对insert操作)
SQL 的四种隔离级别
针对以上会出现的并发一致性问题,SQL设计了不同的隔离级别,每种级别都能解决一些上面的问题。
- 未提交读:最低的隔离级别,只能解决脏写的问题
- 已提交读:一个事务只能读到其他事务已经修改的数据,可以解决不可重复读的问题
- 可重复读:MySQL的默认个隔离级别,可以解决不可重复读的问题
- 可串行化:在这种隔离级别下,幻读也是不可能发生的
MVCC
MVCC 就是多版本并发控制(Multi-Version Concurrency Control),实现原理就是刚刚讲 undo 日志时提到的在 update/delete 操作时通过每条记录的 roll_pointer 属性构建起来的版本链。版本链的头节点就是该记录的最新值,并且每个版本中还包含生成该版本的事务id。通过版本的方式我们就能实现已提交读和可重复读 的隔离级别。对于未提交读的隔离级别来说,直接读取记录最新的版本就好了,对于可串行化的隔离级别来说,是使用加锁的方式来访问记录。
下面来看一下在版本链的基础上已提交读和可重复读具体是如何实现的,MySQL 设计了一个 ReadView 的概念,ReadView 在生成的时候会包含下面的一些属性:
m_ids
:表示在生成 ReadView 时当前系统中活跃的读写事务的事务id列表。min_trx_id
:表示在生成 ReadView 时当前系统中活跃的读写事务中最小的事务 id,也就是 m_ids 中的最小值。max_trx_id
:表示生成 ReadView 时系统中应该分配给下一个事务的 id 值。之前说过事务 id 是全局递增的creator_trx_id
:表示生成该 ReadView 的事务的事务 id。如果该事务只有读操作的话事务 id 就是 0
一个事务有了 ReadView 之后,在访问某条记录之前,可以通过下面的步骤判断到底需要访问该记录的哪个版本:
- 从版本链头节点依次往下找,对于每个版本:
- 如果被访问版本的事务 id 属性值与 ReadView 中的
creator_trx_id
值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。 - 如果被访问版本的事务 id 属性值小于 ReadView 中的
min_trx_id
值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。 - 如果被访问版本的事务 id 属性值大于或等于 ReadView 中的
max_trx_id
值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。 - 如果被访问版本的事务 id 属性值在 ReadView 的
min_trx_id
和max_trx_id
之间,那就需要判断一下事务 id 属性值是不是在m_ids
列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
如果查询到版本链的最后一条记录也对该事务不可见,那么查询结果就不包含该记录。
有了 ReadView,实现已提交读和可重复读的方式也很简单,两者的区别就在于生成 ReadView 的时机不同。已提交读是在事务每次读取数据前都生成一个 ReadView,而可重复读则是在第一次读取数据时就生成一个 ReadView,之后在同一个事务中读取数据不会生成新的 ReadView。按照上面的过程推演一下,就知道不同的生成时机可以带来不同的隔离级别了。
从上面的访问过程可以看出,MVCC 可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: MySQL 事务之 undo log
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论