MySQL 事务底层原理和 MVCC 机制
1 数据库事务
数据库事务是一个跟关键的概念,事务的特性就是我们经常说的 ACID,一句话解释就是要么全都成功要么全都失败。
原子性
一致性
隔离性
持久性
2 MySQL 事务底层原理和实现机制
MySQL 事务机制的核心是两个日志文件:
redo log(重做日志)
undo log(回滚日志)
2.1 redo log
redo log 主要实现的是事务中的持久性,记录物理数据变化即 DML 操作
redo log 分为两部分:redo log file 和 redo log buffer ,redo log file 负责将内容存储到磁盘,redo log buffer 负责将内容加载到内存
重要概念:日志先行
2.2 undo log
undo log 主要实现的是事务中的原子性,负责日志的回滚操作,并实现 MVCC
如何回滚?
答:执行逆向 SQL 语句实现回滚操作,比如操作的语句是 insert into ...,回滚时 undo log 就会执行 delete form ...
3 MVCC 机制
3.1 事务的隔离级别
可以看下我之前写过的一篇文章:MySQL事务隔离级别
为什么要提到 MySQL 事务的隔离级别呢,因为 MVCC 机制只在两个事务隔离级别下才能正常工作,分别是
read-committed
repeatable-read
3.2 MVCC 机制
MVCC,全称 Multi-Version Concurrency Control,即多版本并发控制
,指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了 InnoDB 的并发度。
那么 MySQL 是如何实现 MVCC 的呢?
答:三大法宝,隐藏字段、undo log、read view,分别分析下
3.2.1 隐藏字段
TRX_ID 事务 ID:最后一次修改该记录的事务 ID
ROLL_PTR 回滚指针:指向这条记录的上一个版本的字段
ROW_ID 隐含的自增 ID:如果数据表没有主键,InnoDB 会自动以 ROW_ID 产生一个聚簇索引
所以,这三个隐藏字段的出现,可以让我们把 MySQL 数据表当做早期的 HashMap,即数组+链表,每一个表的每一行字段的一个隐藏字段都是指向它修改之前的数据的,但是数据并没有存储在数据表中,而是存储在 MySQL 日志中。
3.2.2 undo log
上边已经讲过了,undo log 就是将 SQL 语句逆向的执行,分为两种,一种是 insert 语句的逆向,另一种这是 update 语句的逆向。
3.2.3 read view
什么是 Read View,说白了 Read View 就是事务进行快照读
操作的时候生产的读视图
(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID(当每个事务开启时,都会被分配一个 ID, 这个 ID 是递增的,所以最新的事务,ID 值越大)
所以我们知道 Read View
主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View
读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log
里面的某个版本的数据。
Read View遵循一个可见性算法,主要是将
要被修改的数据的最新记录中的
DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果
DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过
DB_ROLL_PTR回滚指针去取出
Undo Log中的
DB_TRX_ID再比较,即遍历链表的
DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的
DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新
老版本
trx_list 事务列表
一个数值列表,用来维护 Read View 生成时刻系统正活跃的事务 ID
up_limit_id 最小的ID
记录 trx_list 列表中事务 ID 最小的 ID
low_limit_id 事务ID的最大值+1
ReadView 生成时刻系统尚未分配的下一个事务 ID,也就是目前已出现过的事务ID的最大值+1
首先比较
DB_TRX_ID < up_limit_id
, 如果小于,则当前事务能看到DB_TRX_ID
所在的记录,如果大于等于进入下一个判断接下来判断
DB_TRX_ID 大于等于 low_limit_id
, 如果大于等于则代表DB_TRX_ID
所在的记录在Read View
生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断判断
DB_TRX_ID
是否在活跃事务之中,trx_list.contains(DB_TRX_ID)
,如果在,则代表我Read View
生成时刻,你这个事务还在活跃,还没有 Commit,你修改的数据,我当前事务也是看不见的;如果不在,则说明,你这个事务在Read View
生成之前就已经 Commit 了,你修改的结果,我当前事务是能看见的
参考链接:https://www.cnblogs.com/xuwc/p/13873611.html
版权声明: 本文为 InfoQ 作者【Barry Yan】的原创文章。
原文链接:【http://xie.infoq.cn/article/e85b035959c97f9c104ee06a9】。文章转载请联系作者。
评论