往期精彩
序
全称Multi-Version Concurrency Control
,多版本并发控制。
其思想就是保存数据的历史版本,通过对数据行的多个版本管理来实现数据库的并发控制,解决了读写阻塞
,避免死锁
,一致性读
等问题。
MySQL的innodb引擎
基于表隐藏字段
,undo log
,readView
来实现了MVCC
。
快照读与当前读
快照读是不加锁的读,语法形式为简单的SELECT
,如
-- 快照读
select * from goods;
与快照读对应的就是当前读,语法形式为:
-- 当前读
select * from goods where id=1 lock in share mode;
select * from goods where id=1 for update;
当前读就是读最新数据,需要加锁。
表隐藏字段
MySQL表中除了用户自己定义的字段外,还会自动生成隐藏字段。如下:
字段名 | 说明 |
---|---|
DB_TRX_ID | 记录最近修改这条记录的事务ID(自增) |
DB_ROLL_PTR | 回滚指针。配合undo log 指向行记录的上个版本 |
DB_ROW_ID | 隐式主键。没有指定主键时自动生成 |
可以使用
ibd2sbi xxx.ibd
命令查看
undo log
回滚日志,其以链表形式存储行记录的历史版本。大概如下图表现形式:
ReadView
ReadView
是读取undo log
中哪条数据的依据,每个事务拥有各自的ReadView,以下简称快照。包含4个核心字段
字段名 | 说明 |
---|---|
m_ids | 生成这个快照时处于活跃状态的事务ID的有序列表 |
m_up_limit_id | 低水位线,事务ID小于m_up_limit_id的事务都可见,意为m_ids中的最小值,小于m_up_limit_id的事务都已提交或回滚 |
m_low_limit_id | 高水位线,事务ID大于等于 m_low_limit_id 的事务都不可见,其意为下一个待分配的事务ID(大于所有已分配的事务ID) |
m_creator_trx_id | 创建快照的事务ID |
判断是否可见规则
在ReadView
中, 有一个changes_visible()
方法,用于判断某个事务的更改对当前事务是否可见。
- 事务ID小于
m_up_limit_id
或等于m_creator_trx_id
可见。 - 事务ID大于等于
m_low_limit_id
不可见。 - 事务ID小于
m_low_limit_id
并且m_ids
为空时可见。 - 当事务ID∈ [
m_up_limit_id
,m_low_limit_id
)时,如果事务ID存在m_ids
中,说明事务未提交则不可见,如果不在则可见。
快照生成和关闭
快照的生成定义在ReadView* trx_assign_read_view()
方法中:
- 执行普通的
SELECT
会生成 - 使用
start transaction with consistent snapshot
会生成,但此指令只在RR级别有效 - 在RC级别中,快照在
SELECT
语句结束时关闭(每次SELECT
都会生成最新的快照) - 在RR级别中,快照在事务结束时关闭(整个事务期间,复用第一次生成的快照)
判断是否可见流程
事务的ID是一个64位的非负整数,只有读写事务会分配事务ID,只读事务是不会分配ID的。
此流程针对主键索引或没有索引。
如上图所示,在RC级别中:(注意ReadView
的变化哦)
- 第一次查询,针对V1的数据,符合
DB_TRX_ID
小于m_up_limit_id
,所以可以查询到age=10
的记录 - 第二次查询,针对V2的数据,事务2修改了数据,所以
undo log
会添加一条数据。但是age=20
的记录不满足可见规则,读到了age=10
的数据(解决了脏读) - 第三次查询,针对V2的数据,事务2已提交,
age=20
的记录符合规则4,所以查到了age=20
的数据(注意咯,不可重复读出现了)
上面是RC级别的分析,接下来看RR级别是怎么解决不可重复读的。
如上图所示,在RR级别中:(注意ReadView
的变化哦)
- 第一次查询,针对V1的数据,符合
DB_TRX_ID
小于m_up_limit_id
,所以可以查询到age=10
的记录 - 第二次查询,针对V2的数据,事务2修改了数据,所以
undo log
会添加一条数据。但是age=20
的记录不满足可见规则,读到了age=10
的数据(解决了脏读) - 第三次查询,针对V2的数据,事务2已提交,在RC级别中,
age=20
的记录符合规则4,但是RR级别中,其不满足所有规则,所以最终查到了age=10
的数据(可重复读了!)
总结
MySQL基于MVCC
实现了到RR级别的隔离性,如果需要解决幻读
和达到串行化的隔离级别,则需要加锁。所以可以说MVCC
+锁
实现了隔离性。