共计 6811 个字符,预计需要花费 18 分钟才能阅读完成。
- GreatSQL 社区原创内容未经受权不得随便应用,转载请分割小编并注明起源。
- GreatSQL 是 MySQL 的国产分支版本,应用上与 MySQL 统一。
-
- 前情提要
- 以后读
- 快照读
-
什么是 MVCC
- 三个暗藏字段
- Undo Log 回滚日志
- MVCC 版本链
- ReadView 读视图
-
不同隔离级别下 MVCC 剖析
- READ-COMMITTED 隔离级别
- REPEATABLE-READ 隔离级别
前情提要
事务有四大个性 ACID
别离是:原子性(Atomicity)
、一致性(Consistency)
、隔离性(Isolation)
、持久性(Durability)
其中隔离性是通过数据库的 锁
加上 MVCC(多版本并发管制)
来保障的。
在介绍 MVCC 之前先来理解一下以后读和快照读。
以后读
以后读读取的是记录的最新版本。同时在读取的时候还要保障其余的并发事务不能更改以后记录,那么以后读会对它要读取的记录进行加锁。不同的操作会加上不同类型的锁,如:SELECT ... LOCK IN SHARE MODE(共享锁)
,SELECT ... FOR UPDATE、UPDATE、INSERT、DELETE(排他锁)
。
快照读
简略的不加锁的 SELECT 就是快照读,快照读读取的是快照生成时的数据,不肯定是最新的数据,它是不加锁的非阻塞读。而不同隔离级别下,创立快照的机会也不同:
READ-COMMITTED(读已提交)
:事务每次 SELECT 时创立 ReadViewREPEATABLE-READ(可反复读)
:事务第一次 SELECT 时创立 ReadView,后续始终应用
在 MySQL 默认隔离级别 (REPEATABLE-READ) 下,快照读保障了数据的可反复读。
什么是 MVCC
MVCC 全称 Multi-Version Concurrency Control
,即多版本并发管制。它是一种并发管制的办法,它能够保护一个数据的多个版本,用更好的形式去解决读写抵触,做到即便有读写抵触也能不加锁。MySQL 中 MVCC 的具体实现,还须要依赖于表中的 三个暗藏字段
、Undo Log 日志
以及ReadView
。
三个暗藏字段
mysql> SHOW CREATE TABLE stu \G;
*************************** 1. row ***************************
Table: stu
Create Table: CREATE TABLE `stu` (
`id` int NOT NULL,
`name` varchar(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
mysql> SELECT * FROM stu;
+----+--------+
| id | name |
+----+--------+
| 1 | m |
| 2 | f |
+----+--------+
当创立了上述这张表后,咱们在查看表构造时只能看到 id、name 字段,实际上除了这两个字段外,InnoDB 引擎还主动为咱们增加了三个暗藏字段,见下表:
字段 | 含意 |
---|---|
DB_TRX_ID | 最近批改事务 ID,记录插入这条记录或最初一次批改该记录的事务 ID。 |
DB_ROLL_PTR | 回滚指针,指向这条记录的上一个版本,用于配合 Undo Log,指向上一个版本。 |
DB_ROW_ID | 暗藏主键,如果表构造没有指定主键,将会生成该暗藏字段。 |
咱们能够应用 ibd2sdi 工具来从表空间文件中提取序列化的字典信息(SDI),来验证一下这三个暗藏字段是否存在。
["ibd2sdi"
,
{
"type": 1,
"id": 402,
"object":
{
"mysqld_version_id": 80025,
"dd_version": 80023,
"sdi_version": 80019,
"dd_object_type": "Table",
"dd_object": {
"name": "stu",
"mysql_version_id": 80025,
"created": 20220919023413,
"last_altered": 20220919023413,
"hidden": 1,
"options": "avg_row_length=0;encrypt_type=N;explicit_encryption=0;key_block_size=0;keys_disabled=0;pack_record=1;stats_auto_recalc=0;stats_sample_pages=0;",
"columns": [
{
"name": "id",
"type": 4,
"is_nullable": false,
"is_zerofill": false,
"is_unsigned": false,
"is_auto_increment": false,
"is_virtual": false,
"hidden": 1,
···省略
},
{
"name": "name",
"type": 16,
"is_nullable": false,
"is_zerofill": false,
"is_unsigned": false,
"is_auto_increment": false,
"is_virtual": false,
"hidden": 1,
···省略
},
{
"name": "DB_TRX_ID", #最近批改事务 ID
"type": 10,
"is_nullable": false,
"is_zerofill": false,
"is_unsigned": false,
"is_auto_increment": false,
"is_virtual": false,
"hidden": 2,
···省略
},
{
"name": "DB_ROLL_PTR", #回滚指针
"type": 9,
"is_nullable": false,
"is_zerofill": false,
"is_unsigned": false,
"is_auto_increment": false,
"is_virtual": false,
"hidden": 2,
···省略
}
],
留神:因为这张表里曾经指定了主键为 id 列,所以不会生成暗藏主键 DB_ROW_ID 列。
Undo Log 回滚日志
回滚日志,在增、改、删操作的时候产生的便于数据回滚的日志。当 INSERT
操作的时候,产生的回滚日志在事务提交后可被 立刻删除
。而UPDATE
和DELETE
操作的时候,产生的 Undo Log 日志不仅在进行数据回滚时须要,在进行快照读时也须要,所以 不会立刻被删除
。
Undo Log 详情可见文章:待浩源 Undo Log 文章发表后增加
MVCC 版本链
当有多个并发事务操作一行数据时,对这行数据的批改会产生多个版本,多个版本通过上述的一个暗藏字段 DB_ROLL_PTR
回滚指针指向 Undo Log 数据地址造成一个链表,即MVCC 版本链
。
ReadView 读视图
ReadView 读视图是快照读 SQL 执行时 MVCC 提取数据的根据,记录并保护零碎以后沉闷的事务(未提交的)id。
下面讲过 Undo Log 和 MVCC 版本链,一条数据通过屡次批改会产生多个版本,而快照读是依据不同机会创立的快照获取数据的,那么快照读 SQL 在执行时该读取那个版本的数据就是靠 ReadViw 读视图来决定的。
ReadView 读视图中蕴含了四个外围字段,也是读取数据的判断根据:
字段 | 含意 |
---|---|
m_ids | 以后沉闷的事务 ID 汇合 |
min_trx_id | 最小沉闷事务 ID |
max_trx_id | 预调配事务 ID,以后最大事务 ID+1(因为事务 ID 是自增的) |
creator_trx_id | ReadView 创建者的事务 ID |
ReadView 一共有四种匹配规定:
条件 | 是否拜访 | 阐明 |
---|---|---|
trx_id == creatro_trx_id | 能够拜访该版本 | 成立,阐明数据是以后这个事务更改的。 |
trx_id < min_trx_id | 能够拜访该版本 | 成立,阐明数据曾经提交了。 |
trx_id > max_trx_id | 不能够拜访该版本 | 成立,阐明该事务是在 ReadView 生成后才开启的。 |
min_trx_id <= trx_id <= max_trx_id | 如果 trx_id 不在 m_ids 中,那么能够拜访该版本 | 成立,阐明数据曾经提交。 |
不同隔离级别下 MVCC 剖析
READ-COMMITTED 隔离级别
后面有提到过在 READ-COMMITTED 隔离级别下事务在每次快照读 SQL 执行时创立 ReadView,每次创立的 ReadView 的四个字段对应的值也是不同的,所以在 READ-COMMITTED 隔离级别下每次快照读 SQL 获取的数据可能也是不同的。
上面通过一个 READ-COMMITTED 隔离级别下并发事务的案例来具体看看:
现有四个并发事务同时拜访一条数据:
在上述并发事务中,事务 5 查问了两次 id 为 1 的数据,因为以后的隔离级别设置为了 READ-COMMITTED
,事务在 每次快照读 SQL 执行时
创立一个 ReadView,每次生成的 ReadView 中的四个字段值都不同。那么三次快照读都会依据生成的 ReadView 中的字段进行规定匹配,从而决定返回的数据。接下来看看流程:
事务 5 第一次快照读解读
事务 5 第一次进行查问时生成的 ReadView 以及原数据如下图:
在匹配版本数据前,先与表中数据进行匹配:
该数据对应的 DB_TRX_ID 为 3,此时 MVCC 就会通过 ReadView 带着这条数据去进行规定匹配:
首先是第一条规定 db_trx_id == creator_trx_id
,db_trx_id(3) 不等于 creator_trx_id(5)故不成立;
第二条规定 db_trx_id < min_trx_id
,db_trx_id(3) 不小于 min_trx_id(3)故不成立;
第三条规定 db_trx_id > max_trx_id
,db_trx_id(3) 小于 max_trx_id(6)故不成立;
第四条规定 min_trx_id <= db_trx_id <= max_trx_id
,db_trx_id(3) 在 min_trx(3)与 max_trx_id(6)之间,然而同时处于 m_ids(3,4,5)汇合之中故也不成立。
通过这次匹配,表中最新的数据无奈匹配,故要与 MVCC 版本链中最下面的数据进行规定匹配
与 MVCC 版本链中最上方的版本进行匹配:
第一条规定 db_trx_id(2)不等于 creator_trx_id(5)故不成立;
第二条规定 db_trx_id(2)小于 min_trx_id(3),该版本的数据满足匹配规定中的第二条,阐明数据曾经提交,此时匹配将终止并返回这个版本对应的数据。
事务 5 第二次快照读
因为以后事务的隔离级别为 READ-COMMITTED(读已提交),所以在每次快照读的时候都会创立一个 ReadView,所以事务 5 第二次进行查问时生成的 ReadView 以及原数据如下图:
在匹配版本数据前,先与表中数据进行匹配:
该数据对应的 DB_TRX_ID 为 4,此时 MVCC 就会通过 ReadView 带着这条数据去进行规定匹配:
首先是第一条规定 db_trx_id == creator_trx_id
,db_trx_id(4) 不等于 creator_trx_id(5)故不成立;
第二条规定 db_trx_id < min_trx_id
,db_trx_id(4) 不小于 min_trx_id(4)故不成立;
第三条规定 db_trx_id > max_trx_id
,db_trx_id(4) 小于 max_trx_id(6)故不成立;
第四条规定 min_trx_id <= db_trx_id <= max_trx_id
,db_trx_id(4) 在 min_trx(4)与 max_trx_id(6)之间,然而同时处于 m_ids(4,5)汇合之中故也不成立。
通过这次匹配,表中最新的数据无奈匹配,故要与 MVCC 版本链中最下面的数据进行规定匹配
与 MVCC 版本链中最上方的版本进行匹配:
第一条规定 db_trx_id(3)不等于 creator_trx_id(5)故不成立;
第二条规定 db_trx_id(3)小于 min_trx_id(4),该版本的数据满足匹配规定中的第二条,阐明数据曾经提交,此时匹配将终止并返回这个版本对应的数据。
REPEATABLE-READ 级别
当初来看看 REPEATABLE-READ 可反复读隔离级别有什么不同的中央。同样,有四个并发事务同时拜访一条数据:
在上述并发事务中,事务 5 查问了两次 id 为 1 的数据,因为以后的隔离级别设置为了 REPEATABLE-READ,事务在第一次快照读 SQL 执行时创立 ReadView,后续该事务所有的快照读都复用该 ReadView。接下来看看流程:
事务 5 第一次快照读解读
事务 5 第一次进行查问时生成的 ReadView 以及原数据如下图:
在匹配版本数据前,先与表中数据进行匹配:
该数据对应的 DB_TRX_ID 为 3,此时 MVCC 就会通过 ReadView 带着这条数据去进行规定匹配:
首先是第一条规定 db_trx_id == creator_trx_id
,db_trx_id(3) 不等于 creator_trx_id(5)故不成立;
第二条规定 db_trx_id < min_trx_id
,db_trx_id(3) 不小于 min_trx_id(3)故不成立;
第三条规定 db_trx_id > max_trx_id
,db_trx_id(3) 小于 max_trx_id(6)故不成立;
第四条规定 min_trx_id <= db_trx_id <= max_trx_id
,db_trx_id(3) 在 min_trx(3)与 max_trx_id(6)之间,然而同时处于 m_ids(3,4,5)汇合之中故也不成立。
通过这次匹配,表中最新的数据无奈匹配,故要与 MVCC 版本链中最下面的数据进行规定匹配
与 MVCC 版本链中最上方的版本进行匹配:
第一条规定 db_trx_id(2)不等于 creator_trx_id(5)故不成立;
第二条规定 db_trx_id(2)小于 min_trx_id(3),该版本的数据满足匹配规定中的第二条,阐明数据曾经提交,此时匹配将终止并返回这个版本对应的数据。
事务 5 第二次快照读解读
因为以后事务的隔离级别为 REPEATABLE-READ(可反复读),所以第二次快照读也会沿用第一次快照读时创立的 ReadView,如下:
在匹配版本数据前,先与表中数据进行匹配:
该数据对应的 DB_TRX_ID 为 4,此时 MVCC 就会通过 ReadView 带着这条数据去进行规定匹配:
首先是第一条规定 db_trx_id == creator_trx_id
,db_trx_id(4) 不等于 creator_trx_id(5)故不成立;
第二条规定 db_trx_id < min_trx_id
,db_trx_id(4) 不小于 min_trx_id(3)故不成立;
第三条规定 db_trx_id > max_trx_id
,db_trx_id(4) 小于 max_trx_id(6)故不成立;
第四条规定 min_trx_id <= db_trx_id <= max_trx_id
,db_trx_id(4) 在 min_trx(4)与 max_trx_id(6)之间,然而同时处于 m_ids(4,5)汇合之中故也不成立。
通过这次匹配,表中最新的数据无奈匹配,故要与 MVCC 版本链中最下面的数据进行规定匹配
与 MVCC 版本链中最上方的版本进行匹配:
第一条规定 db_trx_id(3)不等于 creator_trx_id(5)故不成立;
第二条规定 db_trx_id(3)不小于 min_trx_id(4)故不成立;
第三条规定 db_trx_id 小于 max_trx_id(6)故不成立;
第四条规定 db_trx_id(3)在 min_trx(3)与 max_trx_id(6)之间,然而同时处于 m_ids(3,4,5)汇合之中故也不成立。
通过第二次匹配,MVCC 版本链中最上层的数据版本也无奈匹配,故要与第二条版本进行匹配
与 MVCC 版本链中第二条版本进行匹配:
第一条规定 db_trx_id(2)不等于 creator_trx_id(5)故不成立;
第二条规定 db_trx_id(2)小于 min_trx_id(3),该版本的数据满足匹配规定中的第二条,阐明数据曾经提交,此时匹配将终止并返回这个版本对应的数据。
Enjoy GreatSQL :)
## 对于 GreatSQL
GreatSQL 是由万里数据库保护的 MySQL 分支,专一于晋升 MGR 可靠性及性能,反对 InnoDB 并行查问个性,是实用于金融级利用的 MySQL 分支版本。
相干链接:GreatSQL 社区 Gitee GitHub Bilibili
GreatSQL 社区:
欢送来 GreatSQL 社区发帖发问
https://greatsql.cn/
技术交换群:
微信:扫码增加
GreatSQL 社区助手
微信好友,发送验证信息加群
。