MySQL 事务系列 4
Publish date: Oct 99, 9119
Last updated: Oct 219, 21029
Last updated: Oct 219, 21029
问
MySQL 是如何实现各种隔离级别的呢?
答
未提交读:直接读记录的最新版本即可
串行化:使用加锁的方式读取记录的值
提交读 和 可重复读 的隔离级别下,采用 MVCC + undo 日志 的方式实现
基于 MVCC (Multi-Version Concurrency Control,多版本并发控制)
基于 Undo 日志
MVCC
指在 提交读 和 可重复读 的隔离级别下
事务在执行普通的 SELECT 操作时,访问 记录版本链 的过程
目的
使不同事务的 读-写、写-读 操作能并发执行,从而提高系统性能
解决了 读-写 直接不阻塞的问题,提高事务之间的并发性
实现
对于 InnoDB 引擎的表来说,每条记录都包含两个必要的列:
- trx_id:赋值为 最新的对该记录 做改动 的 事务id 值
- roll_pointer:指向 改动之前 的记录信息
记录格式
mysql > create table hero(number INT, name VARCHAR(50), PRIMARY KEY (number));
mysql > insert into hero values (1, '小红');
它的记录表示如下:
number | name | trx_id | roll_pointer |
---|---|---|---|
1 | 小红 | 1 | &{pre_version} |
版本链
每次对记录改动后,会在 undo 日志中记录旧值,即记录的旧版本
所有版本的记录会被 roll_pointer 串联成一条链表,称为 版本链
ReadView
成因
在 提交读 和 可重复读 的隔离级别下,需要保证以下两点:
- 保证读到 已经提交了 的事务修改过的记录
- 在版本链中找到对 当前事务可见 的记录版本
组成
- m_ids:表示在生成 ReadView 时,当前系统中活跃的读写事务的 id 列表
- min_trx_id:m_ids 中的最小值,即活跃的读写事务的最小 id
- max_trx_id:m_ids 中的最大值 + 1,即系统应该分配个下一个事务的 id
- creator_trx_id:表示生成该 ReadView 的事务 id
规则
record.trx_id:当前访问记录的 trx_id 值
# 当前事务 可以 访问自己修改过的记录
if record.trx_id == ReadView.creator_trx_id:
return true;
# 当前事务 可以 访问已经提交的事务修改过的记录
if record.trx_id < ReadView.min_trx_id:
return true;
# 当前事务 不可以 访问在该事务之后开启的事务修改的记录
if record.trx_id >= ReadView.max_trx_id:
return false;
if record.trx_id between [min_trx_id, max_trx_id]:
# 创建 ReadView 的事务还是活跃的,不可以被访问
if record.trx_id in m_ids:
return false;
# 创建 ReadView 的事务已经被提交,可以访问
else:
return true;
如果某个版本的记录对事务不可见,则遍历链表,找到符合条件的记录为止。
时机
提交读 与 可重复读 隔离级别 最大的区别是:
区别:生成 ReadView 的时机不同
提交读 ——> 事务 在每一次读取数据前 生成一个 ReadView
可重复读 —> 事务 在第一次读取数据时 生成一个 ReadView