FeelingLife FeelingLife
首页
  • Go

    • Go基础知识
  • Python

    • Python进阶
  • 操作系统
  • 计算机网络
  • MySQL
  • 学习笔记
  • 常用到的算法
  • Docker
  • Kubernetes
  • Observability
  • 容器底层
其他技术
  • 友情链接
  • 收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

xuqil

一介帆夫
首页
  • Go

    • Go基础知识
  • Python

    • Python进阶
  • 操作系统
  • 计算机网络
  • MySQL
  • 学习笔记
  • 常用到的算法
  • Docker
  • Kubernetes
  • Observability
  • 容器底层
其他技术
  • 友情链接
  • 收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 环境搭建

  • 事务

    • 事务隔离性和隔离级别
    • MySQL MVCC
      • 什么是 MVCC
      • undo log
        • undo log 是什么
        • undo log 的作用
        • 基于 undo log 的版本链
      • Read View
        • Read View 是什么
        • 在不同隔离级别的 Read View 和版本链访问规则
        • 读已提交(RC)
        • 可重复读(RR)
      • RR 级别下使用 MVCC 是否能避免幻读
      • 小结
  • 数据库优化

  • 日志

  • 《探下MySQL》
  • 事务
xuqil
2023-03-31
目录

MySQL MVCC

# MVCC

# 什么是 MVCC

MVCC(Multi Version Concurrency Control),中⽂名是多版本并发控制,简单来说就是通过维护数据历史版本,从⽽解决并发访问情况下的读⼀致性问题。MySQL 的 MVCC 可以实现,在不同Read View里,同一条记录在系统中可以有多个版本。innodb引擎主要是通过undo log 和事务版本号来实现多版本,利用锁机制来实现并发控制。

在 MySQL InnoDB 存储引擎下,RC(读已提交)、RR(可重复读) 基于 MVCC 进行并发事务控制,MVCC 是基于“数据库版本”对并发事务进行访问的。

关于它的实现,要抓住⼏个关键点,隐式字段、undo⽇志、版本链、快照读&当前读、Read View。

# undo log

innodb引擎会给每张表加一个隐含的列,存储的是事务版本号。当修改数据的时候,会生成一条对应的undo log,undo log一般用于事务回滚,里面含有版本信息。简单来说可以认为undo log存储了历史版本数据。每当发起查询的时候,MySQL 依据隔离级别的设置生成Read View,来判断当前查询可以读取哪个版本的数据。

# undo log 是什么

undo log即事务的回滚日志。比如在事务里执行一个INSERT,那么对应的undo log就是一个DELETE语句。

  • INSERT -> DELETE
  • UPDATE -> UPDATE
  • DELETE -> INSERT

# undo log 的作用

innodb引擎会给每张表加一个隐含的列,存储的是事务版本号。当修改数据的时候,会生成一条对应的undo log。undo log主要用于回滚,里面包含版本信息,它还需要将所有的历史变化记录下来(MySQL 确保版本链数据不再被“引用”后再进行删除),才能准确回滚数据。

创建一个测试用的表,并插入原始数据id=1,c=0:

mysql> create table T(id int primary key, c int) engine=InnoDB;
Query OK, 0 rows affected (0.02 sec)

mysql> insert into T(id, c) values(1, 0);
Query OK, 1 row affected (0.03 sec)
1
2
3
4
5

下面有四个事务,分别在不同时机开启,并在不同时间执行UPDATE和SELECT语句:

事务 A trx_id=1 事务 B trx_id=2 事务 C trx_id=3 事务 D trx_id=4
begin;
update T set c=1 where id=1;
commit;
begin; begin;
update T set c=2 where id=1;
select * from T where id=1;
commit;
begin;
update T set c=3 where id=1;
select * from T where id=1;
commit; commit;

事务 D 分别在不同隔离级别下读到的数据:

  • RC 级别
    • 第一次select得到的结果:c=1
    • 第二次select得到的结构:c=2
  • RR 级别``
    • 第一次select得到的结果:c=1
    • 第二次select得到的结果:c=1

RC 级别下出现了“不可重复读”。

# 基于 undo log 的版本链

还是以上面的事务执行为例子,事务执行过程中 undo log 记录的版本链:

unlog_version_link

版本链又各个事务的回滚段通过链表链接而成,回滚段出了行数据外,还有两个隐式字段:

  • trx_id:事务 ID
  • db_roll_ptr:指向一个回滚段的指针

每个事务都会生成一个版本,事务与事务之间的版本就会形成一条版本链。

例如,事务 C 回滚时,它会根据db_roll_ptr找下一条回滚版本,然后回滚到trx_id=2的版本。

# Read View

在可重复读隔离级别下,事务在启动的时候就“拍了个快照”(Read View)。注意,这个快照是基于整库的。

InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。

而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并把 transaction id 赋值给这个数据版本的事务ID,记为row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。也就是说,数据表中的一行记录,其实可能有多个版本(row),每个版本有自己的row trx_id。

# Read View 是什么

Read View是“快照读” SQL 执行时 MVCC 提取数据的依据。

  • 快照读:快照读是最普通的SELECT查询 SQL 语句。
  • 当前读:指执行下列语句时进行数据读取的方式
    • INSERT、UPDATE、DELETE
    • SELECT ... FOR UPDATE;
    • SELECT ... LOCK IN SHARE MODE;

Read View是一个数据结构,包含4个字段:

  • m_ids:当前活跃的事务编号集合
  • min_trx_id:最小活跃事务编号
  • max_trx_id:已分配事务编号,等于当前最大事务编号+1
  • creator_trx_id:Read View创建者的事务编号

# 在不同隔离级别的 Read View 和版本链访问规则

# 读已提交(RC)

Read View

在每一次执行快照读时都会生成Read View。

MySQL-RC级别下ReadView

版本链数据访问规则

事务 D 第一条SELECT:

MySQL-RC级别下第一条访问版本链

对每个版本按一下规则逐个判断,直到找到了满足规则的版本:

  1. 判断的当前事务 ID 是否等于creator_trx_id(4)。成立则说明数据就是自己这个事务更改的,可以访问。
  2. 判断是否满足trx_id < min_trx_id(2)。成立则说明数据以及提交了,可以访问。
  3. 判断是否满足trx_id > max_trx_id(5)。成立则说明事务是在Read View生成以后才开启,不允许访问。
  4. 判断是否满足min_trx_id(2) <= trx_id <= max_trx_id(5)。成立的话就在m_ids数据中对比,如果trx_id不在m_ids里,则代表数据以及提交了,可以访问。

根据Read View可以读到事务 A 提交的数据,即c=1。

事务 D 第二条SELECT:

MySQL-RC级别下第二条访问版本链

对每个版本按一下规则逐个判断,直到找到了满足规则的版本:

  1. 判断的当前事务 ID 是否等于creator_trx_id(4)。成立则说明数据就是自己这个事务更改的,可以访问。
  2. 判断是否满足trx_id < min_trx_id(3)。成立则说明数据以及提交了,可以访问。
  3. 判断是否满足trx_id > max_trx_id(5)。成立则说明事务是在Read View生成以后才开启,不允许访问。
  4. 判断是否满足min_trx_id(3) <= trx_id <= max_trx_id(5)。成立的话就在m_ids数据中对比,如果trx_id不在m_ids里,则代表数据以及提交了,可以访问。

根据Read View可以读到事务 B 提交的数据,即c=2。

# 可重复读(RR)

仅在第一次执行快照时生成Read View,后续快照读复用。

MySQL-RR级别下ReadView

版本链数据访问规则

事务 D 所有SELECT:

MySQL-RC级别下第一条访问版本链

对每个版本按一下规则逐个判断,直到找到了满足规则的版本:

  1. 判断的当前事务 ID 是否等于creator_trx_id(4)。成立则说明数据就是自己这个事务更改的,可以访问。
  2. 判断是否满足trx_id < min_trx_id(2)。成立则说明数据以及提交了,可以访问。
  3. 判断是否满足trx_id > max_trx_id(5)。成立则说明事务是在Read View生成以后才开启,不允许访问。
  4. 判断是否满足min_trx_id(2) <= trx_id <= max_trx_id(5)。成立的话就在m_ids数据中对比,如果trx_id不在m_ids里,则代表数据以及提交了,可以访问。

根据Read View可以读到事务 A 提交的数据,即c=1。

# RR 级别下使用 MVCC 是否能避免幻读

答案:能,但不完全能。

连续多次快照读,Read View会产生复用,没有幻读问题。

特例:当两次快照读之间存在当前读,Read View会重新生成,导致产生幻读。

例子:

创建一个测试表,并插入原始数据:

mysql> create table T(id int primary key, c int, d int) engine=InnoDB;
Query OK, 0 rows affected (0.02 sec)

mysql> insert into T(id, c, d) values(1, 0, 0);
Query OK, 1 row affected (0.03 sec)
1
2
3
4
5
事务 A 事务 B
begin; begin;
select * from T where c=0;
insert into T(id, c, d) values(2, 0, 1);
commit;
update T set d=10;
select * from T where c=0;
commit;

事务 B 第一条SELECT查到的结果:

mysql> select * from T where c=0;
+----+------+------+
| id | c    | d    |
+----+------+------+
|  1 |    0 |    0 |
+----+------+------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7

事务 B 第二条SELECT查到的结果:

mysql> select * from T where c=0;
+----+------+------+
| id | c    | d    |
+----+------+------+
|  1 |    0 |   10 |
|  2 |    0 |   10 |
+----+------+------+
2 rows in set (0.00 sec)
1
2
3
4
5
6
7
8

从结果中可知,事务 B 出现了幻读。这是因为事务 A 新插入了一条数据,同时事务 B 的两条SELECT之间使用了UPDATE语句(当前读),会重新生成Read View。

# 小结

  • innodb引擎会给每张表加一个隐含的列,存储的是事务版本号。当修改数据的时候,会生成一条对应的undo log,undo log一般用于事务回滚,里面含有版本信息。
  • 在不同Read View里,同一条记录在系统中可以有多个版本。这就是 MVCC(Multi Version Concurrency Control)。
  • Read View是“快照读” SQL 执行时 MVCC 提取数据的依据。在 RC 隔离级别下,每一次执行快照读时都会生成Read View;在 RR 隔离级别下,仅在第一次执行快照时生成Read View,后续快照读复用。
  • 每个事务会根据Read View在不同隔离级别下,在undo log寻找符合条件的版本。
上次更新: 2024/05/29, 06:25:22
事务隔离性和隔离级别
SQL优化

← 事务隔离性和隔离级别 SQL优化→

最近更新
01
VXLAN互通实验
05-13
02
VXLAN
05-13
03
VLAN
05-13
更多文章>
Theme by Vdoing | Copyright © 2018-2025 FeelingLife | 粤ICP备2022093535号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式