MVCC是啥? (Multiversion Concurrency Control) 多版本并发控制
是个方法,是个思想。 解决多并发事务操作同一数据库数据,数据保持一致的问题。
怎么做:每个数据行上维护多个版本
当一个事务要对数据进行修改,为该事务创建一个数据快照,而不直接修改实际数据行。
读操作:有多个版本的数据,读最接近,早于事务开始时间的版本
写操作:不动原来的数据。事务重新创建一个副本操作,回滚了就无事发生,提交了就生成新一版数据。事务操作期间t0-t1的读,都读t0的版本。
为防止版本无限增长,定期进行回收,把不需要的旧版本数据删了,释放空间。
如果要读的行正在执行 DELETE
或 UPDATE
操作,读操作不等DELETE
或 UPDATE
操作完成。而是读最近的历史版本 ,这种读法叫快照读 (snapshot read)。
好 有了方法 要实现MVCC,InnoDB具体是怎么做的
为了实现MVCC,InnoDB
准备了三个东西:隐藏字段、Read View、undo log。
InnoDB
存储引擎为每行数据添加三个隐藏字段:
-
DB_TRX_ID
:表示最后一次插入或更新该行的事务 id。delete
被视为更新,会在记录头Record header
中的deleted_flag
字段将其标记为已删除。 -
DB_ROLL_PTR:
回滚指针,指向该行的undo log
。该行未被更新,则为空 -
DB_ROW_ID
:如果没有设置主键且该表没有唯一非空索引,InnoDB
会使用该 id 来生成聚簇索引
会生成一个东西叫快照,即Read View,记录正在运行得事务的ID
这么多版本的数据放在哪里,放在undo log。
一个事务来了,要读a,InnoDB找到a,看Read View属性和DB_TRX_ID,判断是否可见
,可见,直接读走。不可见,InnoDB通过DB_ROLL_PTR
找到undo log中的最近历史版本读。
开始时间不同,每个事务读到的数据版本可能不同,同一个事务中,用户只能看到该事务开始时读的值和事务本身做的修改。
在 InnoDB
存储引擎中 undo log
分为两种:insert undo log
和 update undo log
insert undo log
:指在 insert
操作中产生的 undo log
。insert
操作的记录只对事务本身可见,对其他事务不可见,该 undo log
可以在事务提交后直接删除。不需要进行 purge
操作
update undo log
:update
或 delete
操作中产生的 undo log
。该 undo log
可能需要提供 MVCC
机制,因此不能在事务提交时就进行删除。提交时放入 undo log
链表,等待 purge线程
进行最后的删除
不同事务或者相同事务对同一记录行的修改,会使该记录行的 undo log
成为一条链表,链首就是最新记录,链尾是最早的旧记录。
InnoDB
中,创建一个新事务后,执行每个 select
语句前,都会创建一个快照(Read View),**快照保存当前数据库中正活跃(没有 commit)的事务的 ID 号,**即保存的是系统中当前不应该被本事务看到的其他事务 ID 列表(即 m_ids)。
当用户在这个事务中要读取某个行时,InnoDB
会将该记录行的 DB_TRX_ID
与 Read View
中的一些变量及当前事务 ID 进行比较,判断是否满足可见性条件。
InnoDB
使用 MVCC
生成 Read View
的时机不同:
- RC :
每次select
前都生成一个Read View
(m_ids 列表) 只要提交了都能读 - RR :只在事务开始后
第一次select
前生成一个Read View
(m_ids 列表)同一事务中要读到一样的数据
MVCC+Next-key-lock防止幻读
InnoDB
存储引擎在 RR 级别下通过 MVCC
和 Next-key Lock
来解决幻读问题:
1.执行普通 select
,以 MVCC
快照读读数据
只在事务开启后第一次查询生成 Read View
,一直使用到事务提交。生成 Read View
之后其它事务的更新、插入记录版本对当前事务不可见,实现可重复读和防止快照读下的 "幻读"。
2.执行 select...for update/lock in share mode、insert、update、delete 等当前读
在当前读下,读的都是最新数据,如果其它事务有插入新记录,并且刚好在当前事务查询范围内,就会产生幻读!InnoDB
使用 Next-key Lock解决。执行当前读时,锁定读到的记录和它们的间隙,防止其它事务在查询范围内插入数据。没有新增,就没有幻读。