mysql MVCC 多版本并发控制

本贴最后更新于 980 天前,其中的信息可能已经事过境迁

mysql MVCC 多版本并发控制

在研究 MVCC 之前需要先了解 2 个概念

  1. 当前读

    • 查询操作

      select * from xx for update (排他锁)或者 select * from xx lock in share mode(共享锁)

      我们平时一般不用这个

    • 增删改

      增删改都是当前读操作, 修改语句都需要先读取数据, 判断约束是否正确, 比如主键索引 唯一索引

    当前读总是读取到最新的数据

  2. 快照读

    普通的 select * from xx 平时一般我们用这个

    快照读读取的是历史版本数据

一. 复习 不可重复读 和 幻读

  1. 不可重复读

    1 个事务第一次查询了某条记录,然后过了一会再次查询的时候发现同样 ID 的记录和第一次查询的不一样

    解决方式 隔离级别调整成 可重复读

  2. 幻读

    1 个事务第一次查询列表返回 10 条记录,然后过了一会再次查询的时候发现变成了 11 条记录

    解决方式 隔离级别调整成 串行化

  • 隔离级别 只针对于快照读
  • 注意红色字体, 产生不可重复读 和 幻读的前提 都是查询了 2 次, 也就说说中间的时间点,其他的事务干了 1 些别的事情

二. 隐藏列

我们创建的表,其实有 2-3 个隐藏的列,隐藏主键(如果表有自己的主键,就没有这列),事务 ID,回滚指针

(用户表)

用户名 密码 邮箱 年龄 隐藏主键 DB_ROW_ID 事务 ID DB_TRX_ID 回滚指针 DB_ROLL_PTR
张三 *** *** 11 1 ? ?
李四 *** *** 12 2 ? ?

三. readview

在进行快照读的时候,会生成一个 readview 读视图

readview 有 4 个属性

活跃的事务 ID 集合
(就是没有 commit 的事务)
列表中的最小事务 ID 尚未分配的下一个事务 ID 创建该视图的事务 ID
PS:为什么用中文 因为网上查到的字段有 2 种, 可能是因为数据库版本问题导致的变量名不一样, 但是意思是一样的

四. undolog

在事务中增删改, 都会生成 undolog

每次修改的回滚指针都会指向上一个版本的记录,如果 rollback,就会找到对应的记录恢复

undolog 不会在 commit 之后立刻删除,而是放入待清理的链表,由 purge 线程判断是否有其他事务在使用.来决定是否删除

如下图:

image-20210811165722068

四. 可见性算法(比较的规则)

  1. 当前记录的事务 ID是否小于 readview列表中的最小事务 ID,如果小于,代表了这条记录在这个 readview 创建之前就已经 commit 了,所以能看见,否则进行下一步判断
  2. 当前记录的事务 ID是否大于等于readview 的尚未分配的下一个事务 ID,如是大于等于就代表这条记录是在 readview 之后生成的,所以不能看到,否则进入一下一步判断
  3. 当前记录的事务 ID是否在活跃的事务 ID 集合中, 如果在,就说明这条记录在 readview 创建的时候还没有 commit,也就是说当前事务不能看到该记录,否则就能看到

五. 案例分析 1

在下面时间线之前已经存在了一条张三的记录

隔离级别: 可重复读

ID name age
1 张三 10

时间线 事务 1 事务 2
1 开启事务,分配到事务 ID1
2 开启事务,分配到事务 ID2
3 修改张三的年龄为 20,并且 commit
4 查询张三

结果?

张三的年龄为 20

为何: 因为查询的时候才产生 readview,所以事务 1 查询时在时间线 4 的时候产生的 readview 活跃的事务 ID 只有 1,最小事务 ID 也是 1,下一个分配的事务 ID 是 3

带入到可见性算法 三条全部不成立, 所以能看到

六. 案例分析 2

在下面时间线之前已经存在了一条张三的记录

隔离级别: 可重复读

ID name age
1 张三 10

时间线 事务 1 事务 2
1 开启事务,分配到事务 ID1
2 查询张三的记录,当前年龄为 10
3 开启事务,分配到事务 ID2
4 修改张三的年龄为 20,并且 commit
5 再次查询张三

结果?

张三的年龄为 10

为何: 因为 隔离级别: 可重复读 的时候, 一个事务中的多次读操作, 重复使用第一次读操作产生的 readview

在时间线 2 的时候 产生的 readview 活跃的事务 ID 为1 和 2,最小事务 ID 是 1,下一个分配的事务 ID 是 3

时间线 5 的查询的 readview 还是时间线 2 的 readview

带入到可见性算法 第三条成立, 所以不能看见 undolog 里新的那条年龄 20 的记录

七. 案例分析 3

在下面时间线之前已经存在了一条张三的记录

隔离级别: 读已提交

ID name age
1 张三 10

时间线 事务 1 事务 2
1 开启事务,分配到事务 ID1
2 查询张三的记录,当前年龄为 10
3 开启事务,分配到事务 ID2
4 修改张三的年龄为 20,并且 commit
5 再次查询张三

和案例分析 2 唯一的区别就是隔离级别不一样

结果?

张三年龄为 20

为何: 因为隔离级别: 读已提交  和 隔离级别: 可重复读 的区别就在于, 隔离级别: 读已提交 每次查询都会创建 1 个新的 readview,时间线 2 和时间线 5 产生了 2 个 readview

八. 案例分析 4

在下面时间线之前已经存在了一条张三的记录

隔离级别: 可重复读

ID name age
1 张三 10

时间线 事务 1 事务 2
1 开启事务,分配到事务 ID1
2 查询李四(查询不到)
3 开启事务,分配到事务 ID2
4 插入李四的记录,并且 commit
5 查询李四

结果?

查询不到李四

为何: 因为 隔离级别: 可重复读 的时候, 一个事务中的多次读操作, 重复使用第一次读操作产生的 readview

在时间线 2 的时候 产生的 readview 活跃的事务 ID 为1 和 2,最小事务 ID 是 1,下一个分配的事务 ID 是 3

时间线 5 的查询的 readview 还是时间线 2 的 readview

带入到可见性算法 第三条成立, 所以不能看见 undolog 里新的那条李四的记录

目前看起来好像解决了幻读问题

八. 其他注意点

MVCC 并没有完全解决幻读, 只是在快照读的时候解决了幻读

时间线 事务 1 事务 2
1 开启事务,分配到事务 ID1
2 查询李四(查询不到)
3 开启事务,分配到事务 ID2
4 插入李四的记录,并且 commit
5 修改李四的年龄为 30 或者 全表修改年龄为 30
5 使用 select * from user for update 语句
6 查询李四(时间线 5 发生的话 就能查询到数据)

注意时间线 5 ,有 2 个时间线 5,当中任何 1 个发生,都会使时间线 6 能查询到结果,当事务中有修改操作 或者 手动使用当前读之后(必须包含该记录,where 条件能覆盖到新插入的数据) 之后的查询会重新生成 readview

  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    324 引用 • 1395 回帖 • 3 关注
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    330 引用 • 614 回帖 • 2 关注
  • MVCC
    4 引用 • 4 回帖
2 操作
xiaokedamowang 在 2021-08-13 19:53:13 更新了该帖
xiaokedamowang 在 2021-08-12 01:30:04 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...