MySQL中的MVCC
作用
MVCC在MySQL中主要用于解决事务的隔离性, 具体来说, 不同的事务在同一时间点可以看到数据库不同的时间点快照, 而不会被其他事务的修改所影响, 主要是为了减少锁的使用, 提升系统的并发能力.
实现
-
TxID
: 事务的ID, 一个递增的值, 值越大表示发生的时间越短. -
LastPtr
: 指向之前的版本 -
ReadView
: 对于一个事务的一个操作而言, 一个ReadView
包含以下部分: CurrentTxId, RunningTX, MaxTX- CurrentTxId: 表示当前的事务的ID
- RunningTX: 表示当前同时执行的事务ID
- MaxTX: MAX(RunningTX)
ReadView
事务执行的某一步, 此时获取到了Record, 存在以下可能性
-
Record.TxID <= CurrentID, 表示是在自己前的事务生成的, 数据一定可见
-
Record.TxID >= MaxTX, 表示是将来可能执行的事务, 数据一定不可见:
Record = Record.LastPtr
-
min(RunningTX) <= Record.TxID < MaxTX, 这部分稍微讨论下
a. 如果TxID
IN
RunningTX中, 表示这个事务由未提交事务生成, 不可见b. 如果TxID
NOT IN
RunningTX中, 表示这个事务由已提交事务生成, 可见
MVCC与隔离级别的关系
隔离级别
隔离级别
读未提交: 还没提交时, 更改就会影响到其他的事务
效果最差, 事务A开启后, 做修改, 其他事务可以读到A的修改, 导致判断失误.
读操作 + lock in share mode;
读提交: 提交之后, 其他事务才能看到更改
总是获取最新的commited的日志
只有在A提交之后, B才能查到最新的数据.
所以多个事务完成后, 可能导致B查询的数据是变化的,
所以读提交, 是不可以重复读的.
可重复读: 一个事务在执行过程中, 总是跟这个事务启动时看到的数据是一致的
指向事务开始时的commited的日志
B读取的数据总是和自己启动时的数据是一致的
解决方案
乐观锁
新添加一个字段version
version 和 查询数据保存在一起
如果版本不一致, 等待重试.
悲观锁
对行加锁
update Table SET a = a + 1 where b =1;
这时候不会再读版本数据, 会使用最新的数据.
串行: 无冲突
完全无冲突, 效率极差, 对并发事务支持非常差
从这个角度我们来看事务隔离级别会有一些不同的感受
- 读未提交, 不创建ReadView, 直接读取当前行的当前版本数据, 不考虑任何版本相关信息.
- 读提交, 在每个语句执行时, 都创建一个ReadView, 总是能够获取到最新的已提交数据.
- 可重复读, 只使用事务开始时创建的ReadView
MVCC与Undo-log的关系
Undo log
和实际操作相反的操作
如果insert, 写入delete
如果update, 写入rollback
如果delete, 写入update(delete 是一种特殊的update, 会将行标记为删除, 实际上是一种update)
为了保证事务的原子性和持久性 ACID: 原子性, 一致性, 隔离性, 持久性
如果事务全部成功, undo log为了保证数据成功会将备份数据页覆盖掉
如果事务有失败, 导致事务回滚, 那么会从undo log中取出备份数据页并且写回
因为事务的执行是线性的, 所以undo log 的写入在正常执行的时候也是线性的
而且undo log是写入文件的, 在掉电之后, 可以通过undo log复原事务前的数据, 保证数据的持久性
- 数据一致性:MVCC允许多个事务看到同一数据的不同版本。Undo Log中保存的数据版本是实现这种多版本数据一致性的关键。
- 回滚操作:当一个事务需要回滚时,MVCC需要确保所有数据可以回到事务开始之前的状态。Undo Log记录了足够的信息来撤销一个事务所做的改变。
- 事务隔离级别:不同的隔离级别对应于不同的一致性需求。MVCC通过Undo Log提供的历史数据版本来实现这些隔离级别,比如可重复读(REPEATABLE READ)和读已提交(READ COMMITTED)。