当前位置:   article > 正文

MySQL MVCC详解

mysql mvcc

目录

 前言

MVCC实现原理

UndoLog版本链

 ReadView

MVCC是否可以解决不可重复读与幻读

隔离级别

READ UNCOMMITTED - 读未提交与脏读

READ COMMITTED - 读已提交与不可重复读

REPEATABLE READ - 可重复读与幻读

SERIALIZABLE - 串行化

小结


 前言

    为了提高数据库并发能力,首先应该想到的就是多线程,但是多线程带来的线程安全问题又不得不考虑。通常采用加锁解决,但这个锁的粒度和锁策略是至关重要的。MVCC全称MultiVersioned ConcurrencyControl(多版本并发控制),MVCC使用了锁、UndoLog以及ReadView配合来完成这件事情。

MVCC实现原理

UndoLog版本链

    MVCC的实现是基于 Undo Log 版本链和 ReadView 来完成的,Undo Log做为回滚的基础,在执行Update或Delete操作时,会将每次操作的上一个版本记录在Undo Log中,每条Undo Log中都记录⼀个叫做 roll_pointer 的引用信息,通过 roll_pointer 就可以将某条数据对应的Undo Log组织成⼀个Undo链,在数据行的头部通过数据行中的 roll_pointer 与Undo Log中的第⼀条日志进行关联,这样就构成一条完整的数据版本链。

    每一条被修改的记录都会有一条版本链,体现了这条记录的所有变更,当有事务对这条数据进行修改时,将修改后的数据链接到版本链接的头部。

 ReadView

    每条数据的版本链都构造好之后,在查询时具体选择哪个版本呢?这⾥就需要使用 ReadView 结构来实现了,所谓 ReadView 是⼀个内存结构,顾名思义是⼀个视图,在事务使用 select 查询数据时就会构造⼀个ReadView,里面记录了该版本链的一些统计值,这样在后续查询处理时就不用遍历所有版本链了,这些统计值具体包括:

  • m_ids :当前所有活跃事务的集合(活跃事务:未提交的事务)
  • m_low_limit_id :活跃事务集合中最小事务Id
  • m_up_limit_id :下⼀个将被分配的事务Id,也就是 版本链头的事务Id + 1 
  • m_creator_trx_id :创建当前 ReadView 的事务Id

    构造好 ReadView 之后需要根据一定的查询规则找到唯一的可用版本,会在UndoLog版本连中找到select查询具体记录版本的链头,从链头开始遍历所有版本,根据四步查找规则,判断每个版本是否符合要求,如果某个版本符合要求则返回该版本数据。

  • 第一步:判断该版本是否为当前事务创建,若 m_creator_trx_id 等于该版本事务id,意味着读取自己修改的数据,可以直接访问,如果不等则到第二步
  • 第⼆步:若该版本事务Id < m_up_limit_id (最⼩事务Id),意味着该版本在ReadView⽣成之前已经提交,可以直接访问,如果不是则到第三步
  • 第三步:若该版本事务Id >=  m_low_limit_id (最大事务Id),意味着该版本在ReadView生成之后才创建,所以肯定不能被当前事务访问,因为该条记录无法判断是否提交,所以无需第四步判断,直接遍历下一个版本,如果不是则到第四步
  • 第四步:若该版本事务Id在 m_up_limit_id (最小事务Id)和 m_low_limit_id (最大事务Id)之间,同时该版本不在活跃事务列表中,意味着创建ReadView时该版本已经提交,可以直接访问,如果不是则遍历并判断下一个版本

    这样从版本链头遍历判断到版本链尾,找到⾸个符合要求的版本即可,就可以实现查询到的结果都是已经提交事务的数据,那么就可以解决脏读问题。

MVCC是否可以解决不可重复读与幻读

  • ⾸先幻读无法通过MVCC单独解决,InnoDB在可重复读隔离级别下使用临建锁,锁住某条记录以及该记录之前的间隙,可以解决大部分幻读问题,但无法从根本上解决幻读问题
  • 对于不可重复读问题,在事务中的第⼀个查询时创建⼀个ReadView,后续查询都是⽤这个ReadView进行判断,所以每次的查询结果都是一样的,从而解决不可重复读问题,在REPEATABLE READ 可重复读,隔离级别下就采用的这种方式
  • 如果事务每次查询都创建⼀个新的ReadView,这样就会出现不可重复读问题,因为不同的ReadView中参数可能不一致,那么在UndoLog中找到的数据就可能不一致,在 READ COMMITTED 读已提交的隔离级别下就是这种实现方式

    这些机制加上锁就可以实现MySQL的四种隔离性 

隔离级别

READ UNCOMMITTED - 读未提交与脏读

实现方式

  • 读取时:不加任何锁,直接读取版本链中的最新版本,也就是当前读,可能会出现脏读,不可重复读、幻读问题
  • 更新时:加共享行锁(S锁),事务结束时释放,在数据修改完成之前,其他事务不能修改当前数据,但可以被其他事务读取

存在问题 

    事务的 READ UNCOMMITTED 隔离级别不使用独占锁,所以并发性能很高,但是会出现大量的数据安全问题。

    比如在事务A中执行了一条 INSERT 语句,在没有执行 COMMIT 的情况下,会在事务B中被读取到,此时如果事务A执行回滚操作,那么事务B中读取到事务A写入的数据将没有意义,这个现象叫做 "脏读"。

注意:

    由于 READ UNCOMMITTED 读未提交会出现"脏读"现象,在正常的业务中出现这种问题会产生非常危重后果,所以正常情况下应该避免使用 READ UNCOMMITTED 读未提交这种的隔离级别。

READ COMMITTED - 读已提交与不可重复读

实现方式

  • 读取时:不加锁,但使⽤快照读,即按照 MVCC 机制读取符合 ReadView 要求的版本数据,每次查询都会构造一个新的 ReadView ,可以解决脏读,但无法解决不可重复读和幻读问题
  • 更新时:加独占行锁(X),事务结束时释放,数据在修改完毕之前,其他事务不能修改也不能读取这行数据

存在问题

    为了解决脏读问题,可以把事务的隔离级别设置为 READ COMMITTED ,这时事务只能读到了其他事务提交之后的数据,但会出现不可重复读的问题,核心原因就是每次快照读都会构造新的ReadView。

    比如事务A先对某条数据进行了查询,之后事务B对这条数据进行了修改,并且提交( COMMIT )事务,事务A再对这条数据进行查询时,得到了事务B修改之后的结果,这导致了事务A在同一个事务中以相同的条件查询得到了不同的值,这个现象要"不可重复读"。

REPEATABLE READ - 可重复读与幻读

实现方式

  • 读取时:不加锁,也使⽤快照读,按照MVCC机制读取符合ReadView要求的版本数据,但无论事务中有几次查询,只会在⾸次查询时生成一个ReadView,可以解决脏读、不可重复读,配合Next-Key行锁可以解决一部分幻读问题
  • 更新时:加Next-Key行锁,事务结束时释放,在一个范围内的数据修改完成之前,其他事务不能对这个范围内的数据进行修改、插入和删除操作,同时也不能被查询

存在问题

    事务的 REPEATABLE READ 隔离级别是会出现幻读问题的,在 InnoDB 中使用了Next-Key行锁来解决大部分场景下的幻读问题,那么在不加 Next-Key 行锁的情况下会出现什么问题吗?

    使用 Next-Key 锁,锁住的是当前索引记录以及索引记录前面的间隙,那么在不加 NextKey 锁的情况下,也就是只对当前修改行加了独占行锁(X),这时记录前的间隙没有被锁定,其他的事务就可以向这个间隙中插入记录,就会导致一个问题如下:

    比如事务A查询了一个区间的记录得到结果集A,事务B向这个区间的间隙中写入了⼀条记录,事务A再查询这个区间的结果集时会查到事务B新写入的记录得到结果集B,两次查询的结果集不一致,这个现象就是"幻读“ 。

SERIALIZABLE - 串行化

实现方式

  • 读取时:加共享表锁,读取版本链中的最新版本,事务结束时释放
  • 更新时:加独占表锁,事务结束时释放,完全串行操作,可以解决所有事务问题

存在问题 

    所有的更新都是串行操作,效率极低

小结

    当理解了不同隔离级别下实现原理与所存在的问题时,可以修改数据库隔离级别进行问题重现,理论与实操结合,相信你会有很大收获。

这里提供一些可能会用到的命令

  1. # 开启事务
  2. START TRANSACTION;
  3. # 提交事务
  4. commit;
  5. # 回滚事务
  6. rollback;
  7. # 查看全局作用域隔离级别
  8. SELECT @@GLOBAL.transaction_isolation;
  9. # 查看会话作用域隔离级别
  10. SELECT @@SESSION.transaction_isolation;
  11. # 通过GLOBAL|SESSION分别指定不同作用域下隔离级别
  12. SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level|access_mode;
  13. # 隔离级别level:
  14. REPEATABLE READ # 可重复读
  15. READ COMMITTED # 读已提交
  16. READ UNCOMMITTED # 读未提交
  17. SERIALIZABLE # 串⾏化
  18. # 访问模式access_mode:
  19. READ WRITE # 表⽰事务可以对数据进⾏读写
  20. READ ONLY # 表⽰事务是只读,不能对数据进⾏读写

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号