mysql mvcc机制详解
什么是当前读、快照读?
当前读
当前读指的是读取数据的最新提交版本。底层是通过对数据加锁加锁实现的
当前读sql语句有:
- select lock in share mode
- select for update
- insert
- update
- delete
快照读
快照读指的是读取数据在某个时间点的快照(版本),而不是数据的最新版本
快照读sql语句:普通selcet语句。底层是利用mvcc机制实现的
MVCC是什么?
mvcc就是多版本并发控制。多版本:指mysql维护着行数据的多个版本。
并发控制:在多个事务同时操作某一行记录时,mysql控制返回多个版本的行记录中的某个版本
用来解决什么问题?
用来解决读写冲突问题。
数据库(普通select语句)并发事务时会出现脏读、不可重复读、幻读问题,也需要利用mvcc机制来解决。
流程:一条select语句来查询数据,但是查询的数据有多个版本,这时就要选择其中的某一个版本返回
数据隔离级别和并发问题
脏读 (Dirty Read)、不可重复读 (Non-Repeatable Read) 和 幻读 (Phantom Read) 是数据库事务并发执行时,在没有采取足够隔离措施的情况下,会产生的三类典型问题。
mvcc机制实现了隔离级别,可以解决这三类并发问题
脏读
要解决脏读,就得上读已提交的隔离级别
得保证一个事务只能读取到其他事务已经提交的数据,没提交的就不该读取到
不可重复读
解决不可重复读问题,就得上读已提交隔离级别
一个事务第一次读取之后,后面读取得每一次数据都应该和第一次读取的一样
那就得维护一份数据的多个版本
每个事务修改一次就生成一个新版本,让不同事务读取不同的版本
在读已提交的得隔离级别下,让事务去读已经提交的数据版本,就可以避免脏读
在不可重复读的隔离级别下,让事务每次读到同一个版本的数据,这样每次读到的都是一样的了,就避免的不可重复读的问题
把修改的数据版本记录到undo log中,给表加一个隐藏字段,叫回滚指针
回滚指针指向undolog日志,日志记录了该表的修改记录的各个版本
利用回滚指针将历史版本串联成一个列表,想读哪个,查找即可
那一个事务来查数据,怎么知道要查哪个版本的数据呢?
所以要为每个事务分配一个id,事务id自增分配。通过对比事务id的大小,就知道哪个事务创建的早、哪个晚,记录哪些事务提交了,哪些未提交,让提交的早的事务不要看提交的比较晚的数据即可。所以要给表加上隐藏字段修改数据的事务id,谁修改了这条数据,就把对应的事务id记录下来
readview:4个重要字段:比较复杂,利用数轴来看
记住作用:就是决定在多个版本中,到底该读哪一个版本
undolog记录数据版本
readview去判断这个数据版本对当前事务的可见性
MVCC怎么实现的?
隐藏字段、undolog版本链、readview
undolog版本链、readview是怎么回事?
四种隔离级别怎么实现的
读未提交
不加任何读锁实现的。(假设:我现在在读这条数据,加了读锁,别人就不能修改。不加读锁,别人就可以修改我在读的数据)
读操作不加锁,就可以直接读取最新版本的数据,即使这些数据被其他事务用排他锁锁住(脏读)
会发生:脏读、不可重复读、幻读
读已提交
可以基于锁实现也可以基于mvcc实现(mvcc更常见)
mvcc的实现原理:
每个语句开始时都会生成一个新的快照(读视图readview)。
一个 Read View主要包含以下核心内容:
- m_ids: 一个列表,记录了在本 Read View创建时,系统中所有活跃的(尚未提交的)事务的事务 ID。
- min_trx_id: 记录 m_ids列表中最小的那个事务 ID。
- max_trx_id: 记录在本 Read View创建时,系统已经分配的下一个事务 ID(即当前最大事务 ID + 1)。
- creator_trx_id: 创建这个 Read View的事务自己的 ID(只有该事务自己有写操作时,这个值才不为空)。
每次查询看到的都是这个语句级快照的最新已提交数据。所以在一个事务内,两次相同的查询可能会看到不同的数据(不可重复读)。
可重复读
串行化
读未提交怎么实现?
不用管
串行化:加锁
可重复读怎么实现?(复用readview和mvcc机制)
可重复读会在第一次select时生成一个readview,通过readview判断应该读哪个数据
读已提交怎么实现?
每次select时就会生成一个新的readview