mysql的mvcc原理(多版本并发控制)

    科技2023-11-02  103

    前言

    RR解决脏读、不可重复读、幻读等问题,使用的是MVCC(Multi-Version Concurrency Control),即多版本的并发控制协议。在了解 MVCC 之前,我们先来聊聊隐藏列、Undo log 和 Read View。

    隐藏列

    InnoDB中每行数据除了我们创建的字段外还有有隐藏列,其中隐藏列包含了本行数据的事务id、指向undo log的指针等。

    Undo log

    MVCC 的实现是通过 Undo log 来完成的。当用户读取一行记录时,若该记录已经被其它事务占用,当前事务可以通过 Undo log 读取之前的行版本信息,因为没有事务需要对历史的数据进行修改操作,所以也不需要加锁,以此来实现非锁定读取。

    Undo log(回滚日志)可以查看我之前整理的文章-----mysql日志文件总结

    Read View

    什么是readview

    事务(暂记事务1)在某一时刻给事务系统trx_sys打快照,把当时trx_sys状态(包括活跃读写事务数组)记下来,之后的所有读操作根据其事务id(即trx_id)与快照中的trx_sys的状态作比较,以此判断readview对于事务1的可见性。

    Read View 中大致包含以下内容:

    trx_ids:数据库系统当前活跃事务 ID 集合;low_limit_id:活跃事务中最大的事务 ID +1;up_limt_id:活跃事务总最小的事务 ID;creator_trx_id:创建这个 Read View 的事务 ID。

    比如某个事务,创建了 Read View,那么它的 creator_trx_id 就为这个事务的 ID,假如需要访问某一行,假设这一行记录的隐藏事务 ID 为 t_id,那么可能出现的情况如下:

        如果 t_id < up_limt_id,说明这行记录在这些活跃的事务创建之前就已经提交了,那么这一行记录对该事务是可见的。    如果 t_id >= low_limt_id,说明这行记录在这些活跃的事务开始之后创建的,那么这一行记录对该事物是不可见的。    如果 up_limit_id <= t_id < low_limit_id,说明这行记录可能是在这些活跃的事务中创建的,如果 t_id 也同时在 trx_ids 中,则说明 t_id 还未提交,那么这一行记录对该事物是不可见的;如果 t_id 不在 trx_ids 中,则说明事务 t_id 已经提交了,那么这一行记录对该事物是可见的。

    什么是MVCC

    MVCC, 即多版本并发控制。MVCC 的实现,是通过保存数据在某个时间点的快照来实现的,也就是说,不管需要执行多长时间,每个事务看到的数据都是一致的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

    MVCC举例说明

    # 首先开启两个临时客户端,将默认隔离级别改成RC mysql> set session transaction isolation level READ COMMITTED; Connection id: 7713 Current database: muke Query OK, 0 rows affected (0.06 sec) mysql> select @@tx_isolation; +----------------+ | @@tx_isolation | +----------------+ | READ-COMMITTED | +----------------+ 1 row in set, 1 warning (0.00 sec) # 客户端1 mysql> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) # 首先查看t21 b为4的数据 mysql> select * from t21 where b = 4; +----+---+---+ | id | a | b | +----+---+---+ | 4 | 4 | 4 | +----+---+---+ 1 rows in set (0.00 sec) mysql> update t21 set b = 44 where b = 4; Query OK, 1 rows affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select sleep(10); +-----------+ | sleep(10) | +-----------+ | 0 | +-----------+ 1 row in set (10.01 sec) mysql> rollback; Query OK, 0 rows affected (0.01 sec) # 客户端2 mysql> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) mysql> select * from t21 where b = 4; +----+---+---+ | id | a | b | +----+---+---+ | 4 | 4 | 4 | +----+---+---+ 1 rows in set (0.00 sec) # 在客户端执行select sleep(10);在执行查询语句 mysql> select * from t21 where b = 4; +----+---+---+ | id | a | b | +----+---+---+ | 4 | 4 | 4 | +----+---+---+ 1 rows in set (0.00 sec) mysql> rollback; Query OK, 0 rows affected (0.00 sec)

    从上面执行现象可以看出:客户端1的事务先于客户端2的事务执行,但是发现在客户端1休眠的时候,事务2很快就执行完毕并返回了结果。 如果RC模式下读操作需要获取S锁,但是因为当前已经有了X锁,两个锁是互斥的,理论上S锁需要等待X锁释放才会获取,但是并没有发生这种情况,可以看出读操作并没有使用S锁, 而是使用MVCC实现的。

    在实验中,客户端2 查询的结果是 客户端1 修改之前的记录,也就是那个点的 Read View,根据上面将的 Read View 原理,被查询行的隐藏事务 ID 就在当前活跃事务 ID 集合中。因此,这一行记录对该事物(客户端2中的事务)是不可见的,可以知道 客户端2 查询的 b=4 记录实际就是来自 Undo log 中。我们看到的现象就是同一条记录在系统中存在了多个版本,这就是多版本并发控制(MVCC)。

    图例

    扩展知识

    RR隔离级别(除了Gap锁(间隙锁)之外)和RC隔离级别的差别是创建snapshot时机不同。 RR隔离级别是在事务开始时刻,确切地说是第一个读操作创建read view的;RC隔离级别是在语句开始时刻创建read view的。创建/关闭read view需要持有mutex,会降低系统性能,5.7版本对此进行优化,在事务提交时session会cache只读事务的read view。下次创建read view,判断如果是只读事务并且系统的读写事务状态没有发生变化,即trx_sys的max_trx_id没有向前推进,而且没有新的读写事务产生,就可以重用上次的read view。MVCC 最大的好处是读不加锁,读写不冲突,极大地增加了 MySQL 的并发性。通过 MVCC,保证了事务隔离性。MVCC 为什么只在 RC 和 RR 两个隔离级别下工作?

            RU: 每次只取最新的值,不存在多个值的情况。 串行:每次查询会加读锁,当有更新时会阻塞住,只有等到查询完锁释放后,才会做更新操作,由此可知RU和串行的隔离级别下,只会存在一个值,不存在多个值的情况。MVCC是解决并发情况下,同时可能产生多个值,从多个值中查询出一个准确值的技术,所以RU,串行是用不到MVCC技术

     

     

    学习链接

    MySQL的MVCC在各种隔离级别中发挥的作用

    MySQL · 源码分析 · InnoDB的read view,回滚段和purge过程简介

    深入学习MySQL事务:ACID特性的实现原理

    mysql事务-----四种隔离级别

    mysql日志文件总结

     

    人们以为他们的理性支配言语,偏偏有时反而支配理性。 ---培根

    Processed: 0.019, SQL: 8