博客主页:🏆看看是李XX还是李歘歘 🏆
🌺每天不定期分享一些包括但不限于计算机基础、算法、后端开发相关的知识点,以及职场小菜鸡的生活。🌺
💗点关注不迷路,总有一些📖知识点📖是你想要的💗
⛽️今天的内容是 MySQL的多版本并发控制MVCC ⛽️💻💻💻
数据库并发控制——锁
Multiversion (version) concurrency control (MCC or MVCC) 多版本并发控制 ,它是数据库管理系统一种常见的并发控制。
我们知道并发控制常用的是锁,当线程要对一个共享资源进行操作的时候,加锁是一种非常简单粗暴的方法(事务开始时给 DQL 加读锁,给 DML 加写锁),这种锁是一种 悲观 的实现方式,也就是说这会给其他事务造成堵塞,从而影响数据库性能。
DDL(data defination language)数据库定义语言:数据库定义语言,主要是建表、删除表、修改表字段等操作;
DML(data manipulation language)数据操作语言:数据操作语言,主要是数据库增删改三种操作;
DQL(data query language)数据查询语言:数据查询语言 select操作。
解释一下 乐观锁 和 悲观锁 的概念,主要是概念的理解。
- 悲观锁:当一个线程需要对共享资源进行操作的时候,首先对共享资源进行加锁,当该线程持有该资源的锁的时候,其他线程对该资源进行操作的时候会被阻塞。比如 Java 中的 Synchronized 关键字。
- 乐观锁:当一个线程需要对一个共享资源进行操作的时候,不对它进行加锁,而是在操作完成之后进行判断。(比如乐观锁会通过一个版本号控制,如果操作完成后通过版本号进行判断在该线程操作过程中是否有其他线程已经对该共享资源进行操作了,如果有则通知操作失败,如果没有则操作成功),当然除了版本号还有CAS,如果不了解的可以去学习一下,这里不做过多涉及。
数据库并发控制——MVCC
很多人认为 MVCC 就是一种 乐观锁 的实现形式,而我认为 MVCC 只是一种 乐观的实现形式,它是通过 一种 可见性算法 来实现数据库并发控制。
MVCC 的两种读形式
在讲 MVCC 的实现原理之前,我觉很有必要先去了解一下 MVCC 的两种读形式。
- 快照读:读取的只是当前事务的可见版本,不用加锁。而你只要记住简单的 select 操作就是快照读(select * from table where id = xxx)。
- 当前读:读取的是当前版本,比如特殊的读操作,更新/插入/删除操作
比如:
select * from table where xxx lock in share mode,
select * from table where xxx for update,
update table set....
insert into table (xxx,xxx) values (xxx,xxx)
delete from table where id = xxx
复制代码
MVCC 的实现原理
MVCC 使用了“三个隐藏字段”来实现版本并发控制,我查了很多资料,看到有很多博客上写的是通过 一个创建事务id字段和一个删除事务id字段 来控制实现的。但后来发现并不是很正确,我们先来看一看 MySQL 在建表的时候 innoDB 创建的真正的三个隐藏列吧。
RowID | DB_TRX_ID | DB_ROLL_PTR | id | name | password |
自动创建的id | 事务id | 回滚指针 | id | name | password |
- RowID:隐藏的自增ID,当建表没有指定主键,InnoDB会使用该RowID创建一个聚簇索引。
- DB_TRX_ID:最近修改(更新/删除/插入)该记录的事务ID。
- DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本。
其实还有一个删除的flag字段,用来判断该行记录是否已经被删除。
而 MVCC 使用的是其中的 事务字段,回滚指针字段,是否删除字段。我们来看一下现在的表格(isDelete是我自己取的,按照官方说法是在一行开头的content里面,这里其实位置无所谓,你只要知道有就行了)。
isDelete | DB_TRX_ID | DB_ROLL_PTR | id | name | password |
true/false | 事务id | 回滚指针 | id | name | password |
那么如何通过这三个字段来实现 MVCC 的 可见性算法 呢?
还差点东西! undoLog(回滚日志) 和 read-view(读视图)。
- undoLog: 事务的回滚日志,是 可见性算法 的非常重要的部分,分为两类。
- insert undo log:事务在插入新记录产生的undo log,当事务提交之后可以直接丢弃
- update undo log:事务在进行 update 或者 delete 的时候产生的 undo log,在快照读的时候还是需要的,所以不能直接删除,只有当系统没有比这个log更早的read-view了的时候才能删除。ps:所以长事务会产生很多老的视图导致undo log无法删除 大量占用存储空间。
- read-view: 读视图,是MySQL秒级创建视图的必要条件,比如一个事务在进行 select 操作(快照读)的时候会创建一个 read-view ,这个read-view 其实只是三个字段。
- alive_trx_list(我自己取的):read-view生成时刻系统中正在活跃的事务id。
- up_limit_id:记录上面的 alive_trx_list 中的最小事务id。
- low_limit_id:read-view生成时刻,目前已出现的事务ID的最大值 + 1。
这时候,万事俱备,只欠东风了。下面我来介绍一下,最重要的 可见性算法。
其实主要思路就是:当生成read-view的时候如何去拿获取的 DB_TRX_ID 去和 read-view 中的三个属性(上面讲了)去作比较。我来说一下三个步骤,如果不是很理解可以参考着我后面的实践结合着去理解。
- 首先比较这条记录的 DB_TRX_ID 是否是小于up_limit_id 或者等于当前事务id。如果满足,那么说明当前事务能看到这条记录。如果大于则进入下一轮判断
- 然后判断这条记录的 DB_TRX_ID 是否大于等于low-limit-id。如果大于等于则说明此事务无法看见该条记录,不然就进入下一轮判断。
- 判断该条记录的 DB_TRX_ID 是否在活跃事务的数组中,如果在则说明这条记录还未提交对于当前操作的事务是不可见的,如果不在则说明已经提交,那么就是可见的。
如果此条记录对于该事务不可见且 ROLL_PTR 不为空那么就会指向回滚指针的地址,通过undolog来查找可见的记录版本。
下面我画了一个可见性的算法的流程图
其实 MVCC 是通过 "三个" 隐藏字段 (事务id,回滚指针,删除标志) 加上undo log和可见性算法来实现的版本并发控制。
参考:你真的懂MVCC吗?来手动实践一下?