MVCC 在 MySQL、Oracle、PostgreSQL 都有应用,用于实现事务的隔离特性。现在结合 《MySQL是怎样运行的》的内容理解、归纳、整理下MVCC的实现及思想。
Multiversion Concurrency Control 对版本并发控制。
对于同一行数据,会根据不同事务的DML操作参生不同的版本,让不同事务各自维护自己能看到的版本从而做到事务隔离。
每次操作数据库都会生成日志,那么就可以把同一条记录的多次操作记录按时间顺序链接起来。
按事务开启的时间顺序,为事务颁发一个Id,从而维护“版本”。是讨论MVCC实现的基础。
光有日志记录还不够,每个事务应该要维护自己的 “视野范围”。也就是当前事务到底能看到什么版本的数据,建立 Read View,并与版本链协作,就能约束事务的视野,从而实现隔离。
Read View 包含:
Read View 里面有多个属性,他们是分别用于解决不同问题的。
如何控制当前事务读不到其他事务 未提交 的数据
我们把日志的 trx_id 与 区间 [min_trx_id, max_trx_id ] 的命中关系罗列出来,用于后续讨论问题
trx_id >= Read View. max_trx_id
Read View. max_trx_id > trx_id >= Read View. min_trx_id
trx_id < Read View. min_trx_id
事务生成了 Read View 之后,对数据行进行读取,发现日志中有
trx_id >= Read View. max_trx_id // max_trx_id 表示下一个事务颁发的id, 所以取等号时,该记录也不属于自己的事务
则说明该日志发生在建立 Read View 之后,并且生成该日志的事务是在当前事务之后开启。
至此,我们能够得到一个信息:该日志发生在当前事务开启之后,直接舍弃
trx_id < Read View. min_trx_id
这个比较简单,直接读即可。由于较新的日志一定会置顶,读到 trx_id < Read View. min_trx_id 立马返回数据即可。
新引入两个参数
事务生成了 Read View 之后,对数据行进行读取,发现日志中有
Read View. max_trx_id > trx_id >= Read View. min_trx_id
说明该日志的事务跟建立 Read View 的时机挨得很近,我们还是可以考虑一下这条日志。具体怎么考虑呢?
m_ids 在 Read View 中负责记录建立 Read View 那一刻的 活跃事务。
如果日志中的 trx_id 不在活跃事务列表,侧面说明该日志的事务已经提交,则可以读取它,否则不读。
并不会,DML语句操作语句交给锁处理了,MVCC只是控制读取的数据的版本。
那么DML跟Read View无关吗,当然也不是,使用 creator_trx_id 登记自己操作了的数据。
如果creator_trx_id == 日志中的trx_id,则是自己操作的数据,直接读取即可
RC
MySQL 会对每个查询语句生成 Read View。
RR
RR 的隔离级别比 RC 高,其中隔离性的提高本质上就是调整了 Read View 创建的时机
第一个查询语句生成 Read View,后续的所有查询都 复用这个 Read View,所以每次读取的值,都来源于同一个版本。
MVCC 用 ReadView 的创建实现事务隔离,同时又尽最大努力去读最新的数据。建立 Read View 时,登记一刹那的事务并发情况。读取具体数据库记录的时候,有日志文件和 Read View共同决策是否信任此日志的数据。回顾以下模型:
上文说的日志,是因为日志承载的是一个思想,MySQL是用 undo log支撑MVCC的,而PostgreSQl则不是。
早期写的 InnoDB 如何避免脏读和不可重复读,是不严谨的地方,现在纠正过来,希望持续学习,不断进步。
MVCC 给我的启示是,我们可以尝试用区间命中的思想去分解问题,当命中关系被完全覆盖,我们可以认为问题被分解完成。