MySQL - InnoDB行级锁优化: MVCC (Multi-Version Concurrency Control 多版本并发控制)
一、为何在已有四种隔离级别后,还要引入MVCC?
现代数据库已经提供了四种隔离级别,详情请看:Database - 数据库隔离级别。
然而,以上四种隔离级别还有另一个共同特点,就是幻读、不可重复读、脏读等问题都是由于一个事务在读数据过程中,受另外一个写数据的事务影响而破坏了隔离性。针对这种“一个事务读+另一个事务写”的隔离问题,近年来有一种名为“多版本并发控制”(Multi-Version Concurrency Control,MVCC)的无锁优化方案被主流的商业数据库广泛采用。
MVCC 是一种读取优化策略,它的“无锁”是特指读取时不需要加锁。MVCC 的基本思路是对数据库的任何修改都不会直接覆盖之前的数据,而是产生一个新版副本与老版本共存,以此达到读取时可以完全不加锁的目的。
二、MVCC重点总结
1.背景知识--数据库并发场景有三种:
读-读
:不存在任何问题,也不需要并发控制读-写
:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读写-写
:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
2. MVCC 就是为了实现“读-写”并发时不加锁。
而这个“读”指的就是“快照读
”,而非“当前读”。当前读实际上是一种加锁的操作,是悲观锁的实现。
- 当前读:像 select lock in share mode (
共享锁
), select for update; update; insert; delete (排他锁
)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。 - 快照读:像不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即 MVCC ,可以认为 MVCC 是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
3. MVCC实现原理
实现原理主要是依赖记录中的 3个隐式字段
,undo日志
,Read View
来实现的。
3个隐式字段
- DB_TRX_ID -> 事务 ID (最近修改/插入):记录创建这条记录/最后一次修改该记录的事务 ID, 6 byte.
- DB_ROLL_PTR -> 回滚指针,指向这条记录的上一个版本(存储于 rollback segment 里),7 byte.
- DB_ROW_ID -> 隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以DB_ROW_ID产生一个聚簇索引,6 byte. (只有表格没有指定主键时才会有,原文默认表格无主键)
undo日志
- insert undo log -> 代表事务在 insert 新记录时产生的 undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
- update undo log -> (对 MVCC 有帮助)事务在进行 update 或 delete 时产生的 undo log ; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除
Read View
- Read View 就是事务进行快照读操作的时候生产的读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID (当每个事务开启时,都会被分配一个 ID , 这个 ID 是递增的,所以最新的事务,ID 值越大)
- Read View 主要是用来做可见性判断的。用“当前进行快照读的事务的事务ID”与 当前表格和undo log里的“隐式字段DB_TRX_ID”作比较,来判断当前事务能够看到哪个版本的数据。既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
4. 详细解释
第一步:简单插入一行记录
第二步:事务1(事务ID=1)来更新name信息
- 在事务 1修改该行(记录)数据时,数据库会先对该行加排他锁
- 然后把该行数据拷贝到 undo log 中,作为旧记录,既在 undo log 中有当前行的拷贝副本
- 拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务 ID 为当前事务 1的 ID, 我们默认从 1 开始,之后递增,回滚指针指向拷贝到 undo log 的副本记录,既表示我的上一个版本就是它
- 事务提交后,释放锁
第三步:事务2(事务ID=2)来更新age信息
- 在事务2修改该行数据时,数据库也先为该行加锁
- 然后把该行数据拷贝到 undo log 中,作为旧记录,发现该行记录已经有 undo log 了,那么最新的旧数据作为链表的表头,插在该行记录的 undo log 最前面
- 修改该行 age 为 30 岁,并且修改隐藏字段的事务 ID 为当前事务 2的 ID, 那就是 2 ,回滚指针指向刚刚拷贝到 undo log 的副本记录
- 事务提交,释放锁
第四步:事务2来快照读,生成一个Read View。事务1,3正在写,事务4已提交,(这一步的事务编号,和前几步无关)
事务2的ID,会与 当前依然活跃的事务ID(事务1, 事务3),以及事务ID的最大值(事务4 + 1 = 5,具体逻辑看后文),进行一个复杂的逻辑比较。
最后的结论就是,事务2能读到事务4提交后的结果。
5. MVCC适用的隔离级别
MVCC只在read commited 和 repeatable read 两个隔离级别下才能正常工作。
- serializable串行级别下的快照读会退化成当前读
- read uncommited未提交读更是不可能
--------------------------原文 --------------------------
正确的理解MySQL的MVCC及实现原理
MVCC多版本并发控制
- 什么是 MVCC
- 什么是当前读和快照读?
- 当前读,快照读和 MVCC 的关系
- 隐式字段
- undo日志
- Read View
- 整体流程
- RR 是如何在 RC 级的基础上解决不可重复读的?
- RC, RR 级别下的 InnoDB 快照读有什么不同?
前提概要
什么是 MVCC ?
MVCCMVCC,全称 Multi-Version Concurrency Control ,即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
什么是当前读和快照读?
当前读,快照读和MVCC的关系
MVCC 能解决什么问题,好处是?
MVCC 的实现原理
隐式字段

undo日志
purge从前面的分析可以看出,为了实现 InnoDB 的 MVCC 机制,更新或者删除操作都只是设置一下老记录的 deleted_bit ,并不真正将过时的记录删除。为了节省磁盘空间,InnoDB 有专门的 purge 线程来清理 deleted_bit 为 true 的记录。为了不影响 MVCC 的正常工作,purge 线程自己也维护了一个read view(这个 read view 相当于系统中最老活跃事务的 read view );如果某个记录的 deleted_bit 为 true ,并且 DB_TRX_ID 相对于 purge 线程的 read view 可见,那么这条记录一定是可以被安全清除的。



Read View 读视图

trx_list(名称我随意取的)
- 一个数值列表
- 用于维护 Read View 生成时刻系统 正活跃的事务 ID 列表
up_limit_id
- lower water remark
- 是 trx_list 列表中事务 ID 最小的 ID
low_limit_id
- hight water mark
- ReadView 生成时刻系统尚未分配的下一个事务 ID ,也就是 目前已出现过的事务 ID 的最大值 + 1
- 为什么是 low_limit ? 因为它也是系统此刻可分配的事务 ID 的最小值
整体流程
事务 1 | 事务 2 | 事务 3 | 事务 4 |
事务开始 | 事务开始 | 事务开始 | 事务开始 |
… | … | … | 修改且已提交 |
进行中 | 快照读 | 进行中 | |
… | … | … |



MVCC 相关问题
RR 是如何在 RC 级的基础上解决不可重复读的?
当前读和快照读在 RR 级别下的区别:
事务A | 事务B |
开启事务 | 开启事务 |
快照读(无影响)查询金额为500 | 快照读查询金额为500 |
更新金额为400 | |
提交事务 | |
select 快照读金额为500 | |
select lock in share mode当前读金额为400 |
事务A | 事务B |
开启事务 | 开启事务 |
快照读(无影响)查询金额为500 | |
更新金额为400 | |
提交事务 | |
select 快照读金额为400 | |
select lock in share mode当前读金额为400 |
RC , RR 级别下的 InnoDB 快照读有什么不同?
原文链接
版权声明:本文为CSDN博主「SnailMann」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/SnailMann/article/details/94724197
posted on 2019-12-08 23:15 frank_cui 阅读(1108) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?