1 并发事务带来的问题
当多个事务并发执行的时候就会导致事务并发问题包括脏写、脏读、不可重复读和幻读,而MVCC为了解决不可重复读和幻读。
1.1 不可重复读
同一个事务下,使用相同的查询语句,在不同时刻读到的结果不一致。
1.2 幻读
同一个事务下,两次读取一个范围的数据记录,两次读取的结果不同。
2 当前读&快照读
2.1 当前读
特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
它读取的数据库记录,都是当前最新的版本,会对当前读取的数据进行加锁,防止其他锁事务修改数据。是一种悲观锁操作。
2.2 快照读
快照读的实现是基于多版本并发控制,即MVCC,既然是多版本,那么快照读到的数据不一定是当前最新的数据,有可能是之前历史版本数据。
3 事务隔离级别
事务隔离级别 | 脏读 | 不可重读读 | 幻读 |
---|---|---|---|
读未提交 | 可能 | 可能 | 可能 |
读已提交 | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | 可能 |
串行化 | 不可能 | 不可能 | 不可能 |
4 UndoLog
MySQL中事务一致性由UndoLog来实现的,UndoLog在事务中的主要作用就是回滚事务和多版本并发机制(MVCC)。
UndoLog记录的是逻辑日志,例如我们执行一条insert的语句,UndoLog会记录一条对应的delete语句;update和delete操作也会记录对应的语句,当数据库崩溃重启或者执行回滚事务时,可以从UndoLog读取相应的数据记录进行回滚操作。
InnoDB在数据库每行数据后面添加3个字段:
- DB_TRX_ID
用来标识本行数据最近一次修改的事务标识符我们通常称为事务id
- DB_ROLL_PTR
指向上一个版本的行记录,能够从最新版本的行记录逐级向上
- DB_ROW_ID
这个字段包含一个随着新数据行的插入操作而单调递增的行id,当由InnoDB存储引擎自动产生聚集索引时,聚集索引会包含这个id。
5 MVCC
5.1 快照
按照MySQL官方文档里对快照的解释来看的话应该是生成快照时会结合当前数据库状态定义出Read View,然后基于Read View和一定规则生成快照。快照只会在读已提交和可重复读隔离级别下执行查询语句时生成。
组成内容 | 含义 |
m_ids | 当前活跃的事务编号集合 |
min_trx_id | 最小活跃事务编号 |
max_trx_id | 定义Read View那一刻的数据库中的事务的最大id。 |
creator_trx_id | 定义Read View的事务的id。 |
在MVCC机制下判断查询类容,需要遵循以下规则:
- 如果某版本的数据的DB_TRX_ID与Read View的creator_trx_id相等,说明这个版本的数据最后由当前事务更改,故这个版本的数据对当前事务可见;
- 如果某版本的数据的DB_TRX_ID小于Read View的min_trx_id,说明这个版本的数据最后由已经提交的事务更改,故这个版本的数据对当前事务可见;
- 如果某版本的数据的DB_TRX_ID大于Read View的max_trx_id,说明最后修改这个版本的数据的事务在快照生成时还未创建,故这个版本的数据对当前事务不可见;
- 如果某版本的数据的DB_TRX_ID满足:min_trx_id <= DB_TRX_ID <= max_trx_id,且m_ids包含DB_TRX_ID,说明这个版本的数据最后由未提交的事务更改,故这个版本的数据对当前事务不可见;
- 如果某版本的数据的DB_TRX_ID满足:min_trx_id <= DB_TRX_ID <= max_trx_id,但m_ids不包含DB_TRX_ID,说明这个版本的数据最后由已经提交的事务更改,故这个版本的数据对当前事务可见。
5.2 读已提交
在读已提交事务隔离级别下,每次查询都会生成一个ReadView
- ReadView1
我们发现ReadView1不满足上面任何规则,因此此时name就是“张石”
- ReadView2
第1-3条规则ReadView2都不符合,可以发现目前的min_trx_id=2,max_trx_id=4,m_ids={2,3},如此我们能知道ReadView2的查询结果为张三石
书籍:深入理解分布式事务:原理与实战
参考:MySQL-事务隔离级别与MVCC