Mysql在可重复度事务隔离级别下,同样的sql在同一事务下多次执行查询结果相同,就算有其他事务修改数据,也不会影响当前事务的查询结果。这个隔离性就是靠MVCC(Multi-Version Concurrency Control)机制来保证的。
Mysql在读已提交和可重复读隔离级别下都实现了MVCC机制。但是实现原理不大一样,在读已提交级别是每次select生成一个版本,在可重复读是第一次select生成一个版本。
undo日志也叫回滚日志。innorDb 默认会在表中加3个隐藏字段:
1.DATA_TRX_ID:最近更新这条数据的事务ID,占6字
2.DATA_ROLL_PTR:存放指向上一个事务版本的指针,占7字节(版本链依赖于这个字段)
3.DB_ROW_ID:主键id,占6字节(表没有设置主键时会自动生成一个隐藏的主键id列)
一条数据被 增、删、改 操作后都会生成一条记录存放在 undo log 中(注意读不会生产记录),然后用DATA_ROLL_PTR指向上一个版本,形成一个历史版本链。
read view(一致性视图)。在可重复读隔离级别,当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view,该视图在事务结束之前都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成),这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_trx_id,创建当前事务的id为 creator_trx_id)、已创建的最大事务id(max_trx_id),事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。
undo日志版本链与read-view对比规则如下:
测试数据脚本:
CREATE TABLE `t_version` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ver` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
insert into t_version(ver) values (1);
下面从一个实例开始讲解MVCC的过程。注意当前隔离级别为可重复度
MySQL中有三种开启事务的方式:
- begin
- start transaction
- start transaction with consistent snapshot
前两种方式,MySQL执行后不会马上创建视图,是在执行第一条select语句时才会创建,第三种方式MySQL执行后会马上创建视图。
假设初始化表t_version ,id=1, ver=1 的事务id为1
在T4时刻,事务8修改修改了ver = ver + 1,此时ver=2。于是 undo log 日志版本链记录如下:
在T6时刻,事务5查询id=1的记录。事务5开启事务时事务8还未启动,所以事务5的read-view为[3, 5]。
此时read-view中的参数为:
最小事务id:min_trx_id = 3
创建当前事务id:idcreator_trx_id = 5
最大事务id:max_trx_id = 5
接下来按照上面说的undo日志版本链与read-view对比规则进行对比:
第1步:被访问的事务id:DATA_TRX_ID=8,比最大事务id:max_trx_id=5要大 ,满足第三条,继续对比下一条日志。
第2步:被访问的事务id:DATA_TRX_ID=1,比最小事务id:min_trx_id = 3要小,满足第二条,返回DATA_TRX_ID=1的记录。
在T7时刻,事务5修改修改了ver = ver + 1,注意修改操作与快照无关,那个是由写锁控制的,想明白写锁看我上一篇。所以此时ver=3。于是 undo log 日志版本链记录如下:
在T8时刻,事务5查询id=1的记录。因为是可重复度级别,事务5的read-view保持不变为[3, 5]。
此时read-view中的参数为:
最小事务id:min_trx_id = 3
创建当前事务id:idcreator_trx_id = 5
最大事务id:max_trx_id = 5
接下来按照上面说的undo日志版本链与read-view对比规则进行对比:
第1步:被访问的事务id:DATA_TRX_ID=5,创建当前事务id:idcreator_trx_id = 5 ,满足第一条,查询的这条记录有被自己修改过,直接返回DATA_TRX_ID=5的记录。
在T9时刻,事务3查询 id=1 的记录。事务3开启事务时的read-view为[3]。
此时read-view中的参数为:
最小事务id:min_trx_id = 3
创建当前事务id:idcreator_trx_id = 3
最大事务id:max_trx_id = 3
接下来按照上面说的undo日志版本链与read-view对比规则进行对比:
第1步:被访问的事务id:DATA_TRX_ID=5,比最大事务id:max_trx_id = 3要大 ,满足第三条,说明该被访问的事务在当前事务生成read-view之后开启,继续对比下一条事务。
第2步:被访问的事务id:DATA_TRX_ID=8,同上
第3步:被访问的事务id:DATA_TRX_ID=1,比最小事务id:min_trx_id = 3要小,满足第二条,返回DATA_TRX_ID=1的数据。
看到这里相信原理都会了。另外mysql会判断把不需要的 undo log 清理掉,不会一直积累undo日志撑爆磁盘的。