MySQL 之MVCC
定义
MVCC即多版本控制器,其特点就是在同一时间,不同事务可以读取到不同版本的数据,从而解决脏读、不可重复度的问题。
锁分类
锁粒度
- 行锁
- 表锁
- 页锁
使用方式
- 共享锁(读锁)
- 排它锁(写锁)
思想
- 悲观锁
- 乐观锁
快照读、当前读
当前读
-
读取的是最新的版本,并且对读取的记录加锁,阻塞其他事务同时改动相同记录
-
select … lock in share mode (共享读锁)
-
select … for update
-
delete 、update、insert
-
实现方式
-
next-key 锁
- 行记录锁+ Gap间隙锁
-
-
快照读
-
单纯的select操作
- RC隔离级别下,每次select 都生成一个快照读
- RR隔离级别下,事务中第一次Select,生成对应的快照读
- Serialiable 隔离级别下,快照读会变成当前读
-
实现方式之MVCC
- 隐式字段
隐式字段 说明 DB_ROW_ID 行id DB_TRX_ID 记录创建这条记录上次修改的它的事务ID DB_ROLL_PTR 回滚指针,指向这条记录的上一个版本 DELETE FLAG 记录被更新或删除,这里的删除并不代表真的删除,而是将这条记录的delete flag 设置为true -
undo log
-
被DB_ROLL_PTR字段连接
-
回滚日志
-
回滚操作实现原子性
-
实现对应的MVCC
-
分类
-
insert、update 产生的undo log
- insert 的undo log 事务提交后会被删除,因为新增的数据无历史版本
- update 的 undo log
-
delete 产生的undo log
-
-
实现
user 表为: ID name DB_TRX_ID DB_ROLL_PTR 1 test 执行sql: update user set name = 'test1' where id = '1' 记录为: 1 test1 1 undo log _index undo log index 为: 1 test
-
-
-
-
Read View
执行select时产生,它是有查询的那一时间所有未提交事务ID组成的数组和已经创建的最大事务ID组成的
-
uncommited txIds
查询时所有未提交事务ID组成的数组
-
min_id
数组中最小的事务id
-
max_id
最大的事务id
-
creator_trx_id
当前事务id
-
-
版本链对比规则
-
trx_id < min_id
表示此版本是已经提交的事务生成的,数据可见
-
trx_id > max_id
表示此版本是由未提交的事务生成的,不可见
-
min_Id <=trx_id <= max_id
-
trx_id 在 txIds数组中
- trx_id == creator_trx_id ---------可见
- trx_id != creator_trx_id--------不可见
-
trx_id 不在txIds数组中-----------------可见
-
隔离级别
RUC
- Read Uncommited 未提交读
RC
- Read Commited 已提交读
RR
- Repeatable Read 可重复读
Serializable
- 可串行化
解决问题
脏读
- 读取其他事务未提交的数据
不可重复读
- 当前事务读取一条数据时,另外一个事务修改了这条数据,再次读取时数据不一致
幻读
-
当前事务先读取了某个范围的数据,另外一个事务新增了这个范围的数据,再次读取时结果不一致
- MVCC只能解决部分幻读问题,还是存在幻读问题,解决幻读需要使用间隙锁
案例分析
表 table :user
ID | name | DB_TRX_ID |
---|---|---|
主键 | 名称 | |
1 | test |
案例一
transaction 2 | select |
---|---|
begin transaction | begin |
update user set name = ‘test1’ where id = ‘1’ | |
commit | |
select name from user where id = ‘1’ |
分析:
-
select开始
-
生成read view ,为 trx_ids:【】,max_trx_id:2
-
undo log为:
ID name trx_id 1 test1 2 1 test -
MVCC查找数据:从第一条数据开始 trx_id 为2;2 不在trx_ids中,则数据可见 ,select结果为 test1
案例二
transaction 2 | select |
---|---|
begin transaction | begin |
update user set name = ‘test1’ where id = ‘1’ | |
select name from user where id = ‘1’ | select name from user where id = ‘1’ |
commit |
分析:
-
select开始
-
生成read view ,为 trx_ids:【2】,max_trx_id:2
-
undo log为:
ID name trx_id 1 test1 2 1 test -
MVCC查找数据:从第一条数据开始 trx_id 为2;2<=2<=2 , 2在trx_ids中,左侧的trx_id为2 ,为当前的trx_id,则可见 左侧select结果为:test1,右侧select 则不可见 ,结果为test
案例三
transaction 2 | transaction 3 | select |
---|---|---|
begin transaction | begin transaction | begin |
update user set name = ‘test1’ where id = ‘1’ | ||
commit | ||
update user set name = ‘test2’ where id = ‘1’ | ||
select name from user where id = ‘1’ | ||
commit |
分析:
-
select开始
-
生成read view ,为 trx_ids:【3】,max_trx_id:3
-
undo log为:
ID name trx_id 1 test2 3 1 test1 2 1 test -
MVCC查找数据:从第一条数据开始 trx_id 为3;2<=3<=3 , 3在trx_ids中,且当前trx_id 不等于3,select 则不可见 ,继续往下追踪,trx_id = 2 < 3,则数据可见,select结果为test1
案例四
transaction 2 | transaction 3 | transaction 4 | select |
---|---|---|---|
begin transaction | begin transaction | begin transaction | begin |
update user set name = ‘test1’ where id = ‘1’ | |||
commit | |||
update user set name = ‘test2’ where id = ‘1’ | |||
commit | |||
update user set name = ‘test3’ where id = ‘1’ | |||
select name from user where id = ‘1’ | |||
commit |
分析:
-
select开始
-
生成read view ,为 trx_ids:【4】,max_trx_id:4
-
undo log为:
ID name trx_id 1 test3 4 1 test2 3 1 test1 2 1 test -
MVCC查找数据:从第一条数据开始 trx_id 为4;2<=4<=4 , 4在trx_ids中,且当前trx_id 不等于4,select 则不可见 ,继续往下追踪,trx_id = 3 , 2<=3 <= 4,2不在 trx_ids中,则数据可见,select结果为test2
案例五
transaction 2 | transaction 3 | transaction 4 | select |
---|---|---|---|
begin transaction | begin transaction | begin transaction | begin |
update user set name = ‘test3’ where id = ‘1’ | |||
commit | |||
update user set name = ‘test2’ where id = ‘1’ | |||
commit | |||
update user set name = ‘test1’ where id = ‘1’ | |||
select name from user where id = ‘1’ | |||
commit |
分析:
-
select开始
-
生成read view ,为 trx_ids:【1】,max_trx_id:4
-
undo log为:
ID name trx_id 1 test1 2 1 test2 3 1 test3 4 1 test -
MVCC查找数据:从第一条数据开始 trx_id 为1;2<=2<=4 , 1在trx_ids中,且当前trx_id 不等于2,select 则不可见 ,继续往下追