事务并发执行遇到的问题
- 脏读(未提交读)
- 不可重复读(已提交读)
- 幻读(读出新纪录)
事务隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
未提交读 | 可能 | 可能 | 可能 |
已提交读 | —— | 可能 | 可能 |
可重复读 | —— | —— | —— |
可串行化 | —— | —— | —— |
mysql在可重复读的级别下,极大的程度上避免了幻读。
版本链
对于InnoDB引擎来说,每个主键索引记录中包含了两个隐藏列
隐藏列 | |
---|---|
trx_id | 记录当前正在操作此条记录的事务id |
roll_pointer | 修改时,记录旧的版本到undo日志中, 当前列就是指向旧版本的指针,以便找到修改前的信息 |
示例
- 创建一张表
CREATE TABLE teacher ( number INT,
name VARCHAR(100), domain varchar(100), PRIMARY KEY (number)
) Engine=InnoDB CHARSET=utf8;
- 新增一条记录,并且当前插入的事物id为60
INSERT INTO teacher VALUES(1, 'Jack', '源码系列');
此刻生成一条记录,如下图所示
insert- 之后有两个80、120事务更新
trx_id(80) | trx_id(120) |
---|---|
BEGIN | |
BEGIN | |
UPDATE teacher SET name = 'Mark' WHERE number = 1; | |
UPDATE teacher SET name = 'James' WHERE number = 1; | |
COMMIT | |
UPDATE teacher SET name = 'King' WHERE number = 1; | |
UPDATE teacher SET name = '大飞' WHERE number = 1; | |
COMMIT |
每次记录修改都有一条undo日志,每条undo日志都会一个roll_pointer属性。可以将这些undo日志串成链表,我们把这种链表称为版本链。
msyql版本链对该记录每次更新后,都会将旧值放到一条 undo 日志中,就算是该记录的 一个旧版本,随着更新次数的增多,所有的版本都会被 roll_pointer 属性连接成 一个链表,我们把这个链表称之为版本链
ReadView
InnoDB 提出了一个 ReadView 的概念, 包含 4 个比较重要的内容:
id | 说明 |
---|---|
m_ids | 生成 ReadView 时当前系统中活跃的读写事务的事务 id 列表 |
min_trx_id | 生成 ReadView 时当前系统中活跃的读写事务中最小的事务 id,也就是 m_ids 中的最小值。 |
max_trx_id | 生成 ReadView 时系统中应该分配给下一个事务的 id 值 |
creator_trx_id | 生成该 ReadView 的事务的事务 id |
访问某条记录时,使用ReadView,遍历版本链,通过下面规则判断某个节点数据是否可以访问。
- 如果被访问的 trx_id 属性值与 ReadView 中的 creator_trx_id 值相同, 意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
- 如果被访问版本的 trx_id 属性值小于 ReadView 中的 min_trx_id 值,表明 生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当 前事务访问。
- 如果被访问版本的 trx_id 属性值大于或等于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不 可以被当前事务访问。
- 如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间(min_trx_id <= trx_id < max_trx_id),那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的, 该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经 被提交,该版本可以被访问。
- 如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一 个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最 后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务 完全不可见,查询结果就不包含该记录。
在 MySQL 中,已提交读(RC)和可重复读(RR)隔离级别的的一个非 常大的区别就是它们生成 ReadView 的时机不同。
- 已提交读(RC)在每次读取数据前都生成一个 ReadView。
- 可重复读(RR)在第一次读取数据时生成一个 ReadView。
mysql默认的事务隔离级别是可重复读(RC)
mysql可重复读(RR)基本解决了幻读问题,那什么情况下会出现幻读问题呢。
- 假设有T1,T2,两个事务,T1先开启事务。
- T2插入一条记录,并提交事务
- T1查询T2插入的记录,很明显查询不到。
- 如果此刻T1更新了T2插入的那条记录
- 此刻T1再去查询那条记录,这时是可以查到的,这就是幻读
这种情况在实际生产并不多。
网友评论