翼度科技»论坛 编程开发 mysql 查看内容

一文带你搞懂MySQL的MVCC机制

9

主题

9

帖子

27

积分

新手上路

Rank: 1

积分
27
MVCC机制是什么?

MVCC,英文全称
  1. Multiversion Concurrency Control
复制代码
,多版本并发控制。简单理解,就是相当于给我们的MySQL数据库拍个“快照”,定格某个时刻数据库的状态。
那你可能问为什么要拍个“快照”,也就是MVCC机制?
还记得事务的一大特性就是隔离性,一共有4个隔离级别,读未提交,读已提交,可重复读,串行化。

  1. MySQL InnoDB
复制代码
引擎的默认隔离级别可重复读为例,可重复读指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的。
为了保证事务启动到结束整个生命周期看到的数据是一致的, 一般有两种方案:

  • MySQL对数据“读-写”的时候,加锁,其他事务写这条数据时加上锁,其他事务读取的时候阻塞。
  • MySQL可以对事务启动的时候,对数据库拍个“快照”,那么事务运行过程中读取都从这个快照读取,不也是保证数据一致么。
第一种方案存在明显的问题,加锁会引发阻塞,从而降低数据库性能。而MySQL设计者们采用第二种,也就是大名鼎鼎的MVCC,它不仅能够解决不可重复读,还一定程度解决幻读的问题,因为你整个数据库快照都有了,你就知道那个时刻的数据了。
  1. 虽然说SQL标准定义中可重复读隔离级别下会存在幻读的现象,但是不同的数据库厂商可以基于SQL标准下有不同的实现,那么不同隔离级别下发生的现象也会有出入,就拿MySQL的可重复读隔离级别就可以一定程度保证幻读。
复制代码
小结一下:
MVCC在
  1. MySQL InnoDB
复制代码
中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突 ,做到即使有读写冲突时,也能做到不加锁非阻塞并发读,而这个读指的就是快照读 , 而非当前读

什么是快照读和当前读?

前面提到了快照读和当前读,这又有什么不一样呢,什么样的sql语句算是快照读,什么样的又算是当前读呢?

快照读

快照读又叫普通读,也就是利用MVCC机制读取快照中的数据。不加锁的简单的SELECT 都属于快照读,比如这样:
  1. SELECT * FROM user WHERE ...
复制代码

  • 快照读是基于MVCC实现的,提高了并发的性能,降低开销
  • 大部分业务代码中的读取都属于快照读

当前读

当前读读取的是记录的最新版本,读取时会对读取的记录进行加锁, 其他事务就有可能阻塞。加锁的 SELECT,或者对数据进行增删改都会进行当前读。比如:
  1. SELECT * FROM user LOCK IN SHARE MODE; # 共享锁
  2. SELECT * FROM user FOR UPDATE; # 排他锁
  3. INSERT INTO user values ... # 排他锁
  4. DELETE FROM user WHERE ... # 排他锁
  5. UPDATE user SET ... # 排他锁
复制代码

    1. update、delete、insert
    复制代码
    语句虽然没有
    1. select
    复制代码
    , 但是它们也会先进行读取,而且只能读取最新版本。

MVCC机制是咋工作的呢?

前面打个比方说MVCC机制相当于是基于整个数据库“拍了个快照”,这时,你会说这看上去不太现实啊。如果一个库有 100G,那么我启动一个事务,MySQL 就要保存 100G 的数据出来,这个过程得多慢啊,而且也很占用空间啊,根本就不能支持几个事务啊。别急,我们现在来讲解下MVCC机制是如何工作的。

数据的多个版本

首先MySQL
  1. innoDB
复制代码
存储引擎需要支持一条数据可以保留多个历史版本。怎么保留呢?还记得事务日志
  1. undo log
复制代码
吗?
对于使用
  1. InnoDB
复制代码
存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:

    1. trx_id
    复制代码
    ,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在
    1. trx_id
    复制代码
    隐藏列里
    1. roll_pointer
    复制代码
    ,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。
InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。

如上图所示,针对
  1. id=1
复制代码
的这条数据,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被
  1. roll_pointer
复制代码
属性连接成一个链表,我们把这个链表称之为版本链,根据版本链就可以找到这条数据历史的版本。

一致性视图ReadView

利用
  1. undo log
复制代码
日志我们已经保留下了数据的各个版本,那么现在关键的问题是要读取哪个版本的数据呢?
这时就需要用到
  1. ReadView
复制代码
了,
  1. ReadView
复制代码
就是事务在使用
  1. MVCC
复制代码
机制进行快照读操作时产生的一致性视图, 比如针对可重复读隔离级别,是在事务启动的时候,创建一个
  1. ReadView
复制代码
, 那
  1. ReadView
复制代码
种都有哪些关键信息呢?

    1. trx_ids
    复制代码
    : 指的是在创建
    1. ReadView
    复制代码
    时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表, “活跃事务”指的就是,启动了但还没提交的事务
    1. min_trx_id
    复制代码
    : 指的是在创建
    1. ReadView
    复制代码
    时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
    1. max_trx_id
    复制代码
    :这个并不是
    1. m_ids
    复制代码
    的最大值,而是创建
    1. ReadView
    复制代码
    时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1
    1. creator_trx_id
    复制代码
    :指的是创建该
    1. ReadView
    复制代码
    的事务的事务 id, 只有在对表中的记录做改动时(执行
    1. INSERT、DELETE、UPDATE
    复制代码
    这些语句时)才会为 事务分配事务id,否则在一个只读事务中的事务id值都默认为0。

对于当前事务的启动瞬间来说,读取的一个数据版本的trx_id,有以下几种可能:

  • 如果被访问版本的
    1. trx_id
    复制代码
    属性值与
    1. ReadView
    复制代码
    中的
    1. creator_trx_id
    复制代码
    值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  • 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;
  • 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
  • 如果落在黄色部分,那就包括两种情况




    • 若 数据的
      1. trx_id
      复制代码
      1. trx_ids
      复制代码
      数组中,表示这个版本是由还没提交的事务生成的,不可见, 去读取这条数据的历史版本,这条数据的历史版本中都包含了事务id信息,去查找第一个不在活跃事务数组的版本记录。
    • 若 数据的
      1. trx_id
      复制代码
      不在
      1. trx_ids
      复制代码
      数组中,表示这个版本是已经提交了的事务生成的,可见。

这种通过版本链 + 一致性视图 来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制),现在你明白MySQL如何实现了“秒级创建快照”的能力了吧。

还是不懂?举例说明

如果你对MVCC机制的整个流程还是比较模糊,我们现在举例来说明下。
比如
  1. student
复制代码
表中有一个事务id为8的插入记录:
  1. insert into student(id, name, class) values(1, '张三', '一班')
复制代码
我们现在在MySQL的读已提交和可重复读隔离级别下,MVCC机制的整个工作流程。
MySQL中的读未提交和序列化并不需要MVCC机制,读未提交,直接读取别人未提交的数据,而序列化全程用加锁的方式,也用不上MVCC, 大家体会下。

可重复读隔离级别下

可重复读
  1. REPEATABLE READ
复制代码
隔离级别的事务来说,只会在第一次执行查询语句时生成一个
  1. ReadView
复制代码
,之后的查询就不会重复生成了。
  1. begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。
复制代码
事务10事务20事务30beginUPDATE student SET name="李四" WHERE id=1;UPDATE student SET name="王五" WHERE id=1;begin更新了一些其他表的数据beginSELECT * FROM

事务10和20均未提交,现在事务30执行
  1. select
复制代码
, 那么得到的结果是什么呢?

  • 在执行
    1. select
    复制代码
    语句时会先生成一个
    1. ReadView
    复制代码
    ,ReadView的
    1. trx_ids
    复制代码
    列表的内容就是
    1. [10, 20]
    复制代码
    1. min_trx_id
    复制代码
    为10,
    1. max_trx_id
    复制代码
    为21,
    1. creator_trx_id
    复制代码
    为0。
  • 然后从版本链中挑选可见的记录,从图中看出,最新版本的列name的内容是
    1. '王五'
    复制代码
    ,该版本的trx_id值为10,在
    1. trx_ids
    复制代码
    列表内,所以不符合可见性要求,根据
    1. roll_pointer
    复制代码
    跳到下一个版本。
  • 下一个版本的列
    1. name
    复制代码
    的内容是
    1. '李四'
    复制代码
    ,该版本的
    1. trx_id
    复制代码
    值也为10,也在
    1. trx_ids
    复制代码
    列表内,所以也不符合要求,继续跳到下一个版本。
  • 下一个版本的列
    1. name
    复制代码
    的内容是'
    1. 张三
    复制代码
    ',该版本的
    1. trx_id
    复制代码
    值为8,小于
    1. ReadView
    复制代码
    中的
    1. min_trx_id
    复制代码
    值10,说明已经提交了,那么最终返回
    1. '张三'
    复制代码


读已提交隔离级别下

读已提交
  1. READ COMMITTED
复制代码
是每次读取数据前都生成一个
  1. ReadView
复制代码
。基本的规则和流程与可重复读隔离级别一致,这里不做重复赘叙。

总结

本问重点介绍了MVCC机制,以及 MVCC 在
  1. READ COMMITTD
复制代码
  1. REPEATABLE READ
复制代码
这两种隔离级别的事务在执行快照读操作时访问记录的版本链的过程。这样使不同事务的 读-写 、 写-读 操作并发执行,从而提升系统性能。

    1. READ COMMITTD
    复制代码
    在每一次进行普通
    1. SELECT
    复制代码
    操作前都会生成一个
    1. ReadView
    复制代码
    1. REPEATABLE READ
    复制代码
    只在第一次进行普通
    1. SELECT
    复制代码
    操作前生成一个
    1. ReadView
    复制代码
    ,之后的查询操作都重复使用这个
    1. ReadView
    复制代码
    就好了。
以上就是一文带你搞懂MySQL的MVCC机制的详细内容,更多关于MySQL MVCC机制的资料请关注脚本之家其它相关文章!

来源:https://www.jb51.net/database/29149160d.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具