目标:MySQL mvcc
分析:
详解:
第一部分 隔离级别
1、隔离级别
| 脏读 | 不可重复读 | 幻读 | 加锁 | |
|---|---|---|---|---|
| 读未提交 | 是 | 是 | 是 | |
| 读已提交 | 是 | 是 | ||
| 不可重复读 | 是 | |||
| 序列化 | 是 |
- 脏读:指读取到其他事务正在处理的未提交数据。
- 不可重复读:指并发更新时,另一个事务前后查询相同数据时的数据不符合预期。
- 幻读:指并发新增、删除这种会产生数量变化的操作时,另一个事务前后查询相同数据时的不符合预期。
2、脏读
| 事务A | 事务B |
|---|---|
| begin | begin |
| select name from s where id=1(张三) | |
| update s set name='李四' where id=1 | |
| select name from s where id=1(李四) | |
| rollback | |
| commit |
事务A读取到了事务B未提交(新增、更新、删除)的数据。
3、不可重复读
| 事务A | 事务B |
|---|---|
| begin | begin |
| select name from s where id=1(张三) | |
| update s set name='李四' where id=1 | |
| commit | |
| select name from s where id=1(李四) | |
| commit |
事务A在同一事务中的两次读取数据不一致。事务B已提交(更新)
4、幻读
| 事务A | 事务B |
|---|---|
| begin | begin |
| select * from s(all) | |
| delete from s(清空) | |
| insert into s set values(1,'张三') | |
| commit | |
| select * from s(all)(张三) | |
| commit |
事务A最后一次读取到了数据。事务B已提交(新增、删除)
第二部分 mvcc
1、ReadView是什么?
ReadView是快照读SQL执行时MVCC提取数据的依据,快照读就是普通的Select查询语句。使用mvcc。
当前读指代执行下列语句时进行数据读取的方式,insert、update、delete、select...for update、select...lock in share mode。使用间隙锁。
ReadView是一个数据结构,包含4个字段
- m_ids: 当前活跃的事务编号集合
- min_trx_id: 最小活跃事务编号
- max_trx_id: 预分配事务编号,当前最大事务编号+1
- creator_trx_id: ReadView创建者的事务编号
undo log版本链数据访问规则: 当前事务id : trx_id
- 1、判断 trx_id=creator_trx_id 吗?成立说明数据就是自己这个事务更改的,可以访问。
- 2、判断 trx_id<min_trx_id 吗?成立说明数据已经提交了,可以访问。
- 3、判断 trx_id>max_trx_id 吗?成立说明该事务是在ReadView生成以后才开启,不允许访问。
- 4、判断 min_trx_id<=trx_id<=max_trx_id 吗?成立在m_ids数据中对比,不存在的则代表数据已提交,可以访问。
2、读已提交
读已提交:在每一次执行快照读时生成ReadView。
| 事务A trx_id=1 | 事务B trx_id=2 | 事务C trx_id=3 | 事务D trx_id=4 |
|---|---|---|---|
| begin | begin | begin | begin |
| update s set name="李四" where id=1 | |||
| commit | update s set name="王五" where id=1 | ||
| select * from s where id=1(李四) (ReadView 1) | |||
| commit | |||
| update s set name="赵六" where id=1 | |||
| select * from s where id=1(王五) (ReadView 2) | |||
| commit | |||
| commit |
ReadView 1
- m_ids={2,3,4}
- min_trx_id=2
- max_trx_id=5
- creator_trx_id=4
版本链数据访问顺序 : \
trx_id=3 不符合 \
trx_id=2 不符合
trx_id=1 符合 (读取到数据 李四)
ReadView 2
- m_ids={3,4}
- min_trx_id=3
- max_trx_id=5
- creator_trx_id=4
版本链数据访问顺序 : \
trx_id=3 不符合 \
trx_id=2 符合 (读取到数据 王五)
3、不可重复读
3-1 不可重复读:仅在第一次执行快照读时生成ReadView,后续快照读复用。
| 事务A trx_id=1 | 事务B trx_id=2 | 事务C trx_id=3 | 事务D trx_id=4 |
|---|---|---|---|
| begin | begin | begin | begin |
| update s set name="李四" where id=1 | |||
| commit | update s set name="王五" where id=1 | ||
| select * from s where id=1(李四) (ReadView 1) | |||
| commit | |||
| update s set name="赵六" where id=1 | |||
| select * from s where id=1(王五) (ReadView 1) | |||
| commit | |||
| commit |
ReadView 1
- m_ids={2,3,4}
- min_trx_id=2
- max_trx_id=5
- creator_trx_id=4
版本链数据访问顺序 : \
trx_id=3 不符合 \
trx_id=2 不符合
trx_id=1 符合 (读取到数据 李四)
ReadView 1
- m_ids={2,3,4}
- min_trx_id=2
- max_trx_id=5
- creator_trx_id=4
版本链数据访问顺序 : \
trx_id=3 不符合 \
trx_id=2 不符合
trx_id=1 符合 (读取到数据 李四)
3-2 连续多次快照读,ReadView会产生复用,没有幻读问题。特例:当两次快照读之间存在当前读,ReadView会更新,导致产生幻读。
当前读,使用的是最新的记录。如果修改了其他事务变更的记录,下次快照读读的数据就包含被修改的记录,只包含被修改的记录,如果都未被修改,下次快照读读的就是上次的ReadView。
| 事务A trx_id=1 | 事务B trx_id=2 | |
|---|---|---|
| begin | begin | |
| select * from s where name="张三" ReadView 1 | ||
| insert into s values (2,"张三",20) | ||
| insert into s values (3,"张三",20) | ||
| commit | ||
| update s set age=25 where id=3 ReadView 2 | ||
| select * from s where name="张三" (ReadView 2) | ||
| commit |
ReadView 1
- m_ids={1}
- min_trx_id=1
- max_trx_id=3
- creator_trx_id=2
版本链数据访问顺序 : \
trx_id=1 不符合 \
trx_id=0 符合 (读取到数据 1条)
ReadView 2
=
ReadView 1
+
id=3的这条数据
不包括id=2的这条数据,因为当前读(update s set age=25 where id=3)并未修改id=2