美文网首页
MySQL-MVCC

MySQL-MVCC

作者: Zeppelin421 | 来源:发表于2022-05-26 10:46 被阅读0次

    问题

    多事务同时访问数据库中的相同数据时

    • 读 + 读:多个事务对相同数据全部是读操作时,不会产生任何并发问题
    • 读 + 写:多个事务即存在读又存在写时,可能会产生脏读不可重复读幻读的问题
    • 写 + 写:多个事务同时修改数据,可能产生数据丢失(回滚丢失、覆盖丢失)

    解决方案

    • 读 + 读:不用解决
    • 读 + 写:常规一般会对要操作的数据加锁来解决并发读写可能产生的问题,MySQL 的 InnoDB 实现了 MVCC 来更好地处理读写冲突,可以做到即使存在并发读写,也不用加锁,实现“非阻塞并发读”
    • 写 + 写:通过加锁(乐观锁/悲观锁)来解决

    概念

    MVCC(Multi Version Concurrency Control)即多版本并发控制,是指在数据库中为了实现高并发的数据访问,对数据进行多版本处理,并通过事务的可见性来保证事务能看到自己应该看到的数据版本。

    简单说,MVCC多版本并发控制是指:维持一个数据的多个版本,使得读写操作没有冲突,这只是一个理想概念。MySQL 通过快照读实现了 MVCC 理想模型的其中一个具体非阻塞读功能。

    • 当前读
      读取的数据是最新版本,读取数据时还要保证其他并发事务不会修改当前的数据,当前读会对读取的记录加锁。比如:select ... lock in share mode(共享锁)、select ... for update | insert | delete(排它锁)
    • 快照读
      基于 MVCC 实现的读,不对读操作加任何锁,读取的时候根据版本链和 Read View 进行可见性判断,所以读取的数据不一定是数据库中的最新值。注意,在串行化隔离级别下,读操作也会加锁,会退化成当前读

    实现原理

    MySQL 中 MVCC 主要是通过行记录中的隐藏字段、版本链、ReadView来实现的

    隐藏字段

    MySQL中,在每一行记录中除了自定义的字段,还有一些隐藏字段

    • ROW_ID:当数据库表没有定义主键时,InnoDB 会以 ROW_ID 为主键生成一个聚簇索引
    • TRX_ID:事务ID记录了新增/最近修改这条记录的事务ID,事务ID是自增的
    • ROLL_POINTER:回滚指针指向当前记录的上一个版本(在Undo Log中)

    版本链

    在修改数据的时候,会向 Redo log 中记录修改的页内容(用于恢复数据),也会向 Undo log 中记录数据库原来的快照。Undo log 有两个作用,回滚事务和实现MVCC

    事务(trx_id=100)执行了 insert into t_user values(1, '张三', 20);


    事务(trx_id=102)执行了update t_user set name = '李四' where id = 1;

    事务(trx_id=103)执行了update t_user set name = '王五' where id = 1;

    ReadView

    多个事务对同一行数据修改后,这行记录除了最新的数据,在 Undo log 中还有多个版本的快照。那其他事务查询时能查到最新版本的数据吗?

    ReadView 就是 MVCC 在对数据进行快照时,会产生的一个“读视图

    ReadView 中有4个比较重要的变量

    • m_ids:活跃事务ID列表,当前系统中所有活跃的(没提交的)事务的事务ID列表
    • mix_trx_id:m_ids中最小的事务ID
    • max_trx_id:生成 ReadView 时,系统应该分配给下一个事务的ID,也就是 m_ids 中的最大事务ID + 1
    • creator_trx_id:生成该 ReadView 的事务的事务ID

    ReadView 可见性算法:

    • trx_id == creator_trx_id 时,说明版本链中的这个版本是当前事务修改的,所以该快照记录对当前事务可见
    • trx_id < min_trx_id 时,说明版本链中的这条记录已经提交了,所以该快照记录对当前事务可见
    • trx_id > max_trx_id 时,说明快照记录对当前事务不可见
    • min_trx_id <= trx_id < max_trx_id 时,如果版本链中记录的 trx_id 在活跃事务ID列表 m_ids 中,说明生成 ReadView 时,修改记录的事务还没有提交,所以该快照记录对当前事务不可见;否则该快照记录对当前事务可见

    当事务对行记录进行快照读时 select * from t_user where id=1;,在版本链的快照中,从最新的一条记录开始,一次判断这4个条件,直到某一版本的快照读对当前事务可见,否则继续比较上一版本的记录

    MVCC 只在 RC(解决脏读)和 RR(解决不可重复读)隔离级别下生效。在 RC 隔离级别下,每一次快照读都会生成一个最新的 ReadView;在 RR 隔离级别下,只有事务中第一次快照读会生成 ReadView,之后的快照读都使用第一次生成的 ReadView。

    MVCC案例

    前提条件:事务(trx_id=100)向表中插入一条记录 insert into t_user values(1, '张三', 20);并提交事务

    时间顺序 事务101 事务102 事务103
    t1 begin
    t2 select * from t_user where id=1;
    t3 begin
    t4 select * from t_user where id = 1;
    t5 begin
    t6 select * from t_user where id = 1;
    t7 update t_user set name = '李四' where id = 1
    t8 select * from t_user where id = 1
    t9 select * from t_user where id = 1
    t10 commit
    t11 select * from t_user where id = 1
    t12 update t_user set name = '王五' where id = 1
    t13 commit
    t14 select * from t_user where id = 1

    版本链

    在时间点 t1~t6 时,整个版本链中只有一个快照, trx_id = 100


    在时间点 t7~t11 时,整个把版本链有两个快照 trx_id = 102、100


    在时间点 t12~t14 时,整个把版本链有两个快照 trx_id = 103、102、100

    事务隔离级别为RC(读已提交)

    当前事务隔离级别为RC时,每个事务每次查询对应生成的 ReadView

    • t2、t4、t6
      这三个时间点,版本链中都只有一个快照(trx_id=100),
      因为 trx_id(100) < min_trx_id(101),符合算法2,所以 trx_id = 100 这个快照对当前事务可见
    • t8
      版本链中有两个快照 trx_id=102 -> trx_id=100,从版本链中最新的开始依次判断
      对于 trx_id = 102 快照,min_trx_id(101) <= trx_id(102) < max_trx_id(104),且 trx_id(102) 在 trx_list(101,102,103) 中,说明当前事务生成 ReadView 时,修改该记录的事务仍在活跃事务(还未提交),根据算法4,trx_id=102的快照对当前事务不可见
      对于 trx_id = 100,因为 trx_id(100) < min_trx_id(101),符合算法2,所以 trx_id = 100这个快照对当前事务可见
    • t9
      版本链中有两个快照 trx_id=102 -> trx_id=100,从版本链中最新的开始依次判断
      对于 trx_id = 102 的快照,因为 trx_id(102) = creator_trx_id(102) 符合算法1,所以 trx_id = 102 的快照对当前事务可见
    • t11
      版本链中有两个快照 trx_id=102 -> trx_id=100,从版本链中最新的开始依次判断
      对于 trx_id = 102 快照,min_trx_id(101) <= trx_id(102) < max_trx_id(104),且 trx_id(102) 不在 trx_list(101,103) 中,说明当前事务生成 ReadView 时,修改该记录的事务不是活跃事务(已提交),根据算法4,trx_id=102的快照对当前事务可见
    • t14
      版本链中有三个快照 trx_id = 103 -> trx_id=102 -> trx_id=100,从版本链中最新的开始依次判断
      对于 trx_id = 103 的快照,min_trx_id(101) <= trx_id(103) < max_trx_id(104),且 trx_id(103) 不在 trx_list(101)中,说明当前事务生成 ReadView 时,修改该记录的事务不是活跃事务(已提交),根据算法4,trx_id=103的快照对当前事务可见

    事务隔离级别为RR(可重复读)

    当前事务隔离级别为RR时,每个事务每次查询对应生成的 ReadView


    在 RR 隔离级别下,只有事务中第一次快照读会生成 ReadView,之后的快照读都使用第一次生成的 ReadView,所以 事务101在 t8、t14时刻查询时,使用的 ReadView 跟 t2 时刻一样;事务102在 t9 时刻查询使用 ReadView 跟 t4 时刻一样;事务103在 t11 时刻查询使用的 ReadView 跟 t6 时刻一样。
    • t2、t4、t6
      这三个时间点,版本链中都只有一个快照(trx_id=100),
      因为 trx_id(100) < min_trx_id(101),符合算法2,所以 trx_id = 100 这个快照对当前事务可见
    • t8
      版本链中有两个快照 trx_id=102 -> trx_id=100,从版本链中最新的开始依次判断
      对于 trx_id = 102 快照,trx_id(102) >= trx_id(102),根据算法3,trx_id=102的快照对当前事务不可见
      对于 trx_id = 100,因为 trx_id(100) < min_trx_id(101),符合算法2,所以 trx_id = 100这个快照对当前事务可见
    • t9
      版本链中有两个快照 trx_id=102 -> trx_id=100,从版本链中最新的开始依次判断
      对于 trx_id = 102 的快照,因为 trx_id(102) = creator_trx_id(102) 符合算法1,所以 trx_id = 102 的快照对当前事务可见
    • t11
      版本链中有两个快照 trx_id=102 -> trx_id=100,从版本链中最新的开始依次判断
      对于 trx_id = 102 快照,min_trx_id(101) <= trx_id(102) < max_trx_id(104),且 trx_id(102) 在 trx_list(101,102,103) 中,说明当前事务生成 ReadView 时,修改该记录的事务是活跃事务(还未提交),根据算法4,trx_id=102的快照对当前事务不可见
    • t14
      版本链中有三个快照 trx_id = 103 -> trx_id=102 -> trx_id=100,从版本链中最新的开始依次判断
      对于 trx_id = 103 的快照,trx_id(103) >= max_trx_id(102),根据算法3,trx_id=103的快照对当前事务不可见
      对于 trx_id = 102 的快照,trx_id(102) >= max_trx_id(102),根据算法3,trx_id=102的快照对当前事务不可见
      对于 trx_id = 100 的快照,trx_id(100) < min_trx_id(101),根据算法2,trx_id=100的快照对当前事务可见

    相关文章

      网友评论

          本文标题:MySQL-MVCC

          本文链接:https://www.haomeiwen.com/subject/dvmbkrtx.html