[数据库] MVCC多版本并发控制
参考博客:https://www.cnblogs.com/xuwc/p/13873611.html
1.MVCC简述
数据库并发操作问题
设想对一个数据库进行并发操作,可能产生以下三种情况:
读-读,没有任何冲突,无需引入额外机制;
读-写,可能产生事务隔离问题,如脏读、不可重复读、幻读问题;
写-写,会产生冲突,第一种是事务A撤销时覆盖掉事务B已经提交的数据,第二种是事务A覆盖掉事务B已经提交的数据;
MVCC机制简述
MVCC的作用是解决读写冲突的无锁并发控制机制。
其实现机制是:为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。
其优势在于:读写操作不会相互阻塞,同时能够解决脏读、不可重复读等问题。
注意:MVCC机制不能解决写-写冲突,需要结合乐观或者悲观锁。
2.当前读和快照读
InnoDB中的读取有两种类型:当前读和快照读。
当前读:读取最新版本,当SQL查询涉及使用锁极为当前都操作。(如使用共享锁的select,使用排他锁的updata、delete等),也就是说当前读的时候会通过锁暂时禁止其他事务修改数据。
快照读:读取不晚于当前事务时间戳的数据,不使用锁,因此读取到的数据可能会是历史版本。
3.MVCC实现原理
MVCC依靠使用记录中的额外部分来实现:隐式字段、undo日志、Read View
隐式字段
DB_TRX_ID
最后一次修改这一记录的事务ID
DB_ROLL_PTR
回滚指针,指向该记录的上一个版本
DB_ROW_ID
隐藏的自增ID
删除FLAG,指示该记录是否已被删除。
很明显,MVCC中每一个记录和其之前的记录形成了一条反序的链表。
undo日志
undo日志可以看作已经过期的记录(及其隐式字段)连成的链表。回滚指针指向的就是undo日志的链首(链尾是最旧的记录)。
分为两种:insert undo log 和 update(delete) undo log
isnert undo log,插入新记录时产生,用在事务回滚中,事务提交后可以丢弃(因为没有回滚到先前版本的需要)
update(delete) undo log,更新记录时产生,在事务回滚和快照读时均需要用到,若日志确实无效,InnoBD会将其deleted_bit标记为true,并由单独的purge线程进行清理。
Read View(读视图)
事务进行快照读时产生,Read View遵循可见性算法,其作用是将要被修改数据最新记录的DB_TRX_ID与当前系统中其他活跃事务的ID对比,如果有冲突,那么通过回滚指针获取undo log中的以前版本,直到遍历到某个老版本记录,其DB_TRX_ID没有产生冲突。
可见性判断中需要用到的数据由MVCC进行全局维护。
理解:事务中创建读视图时机的不同,会导致查询结果的不同。如果事务创建读视图时,有其他事务尚未提交,在事务稍后进行快照读时,即使其他的事务已经提交,也读取不到(因为读视图并没有更新,依然认为那个事务ID有冲突)。
注:当mysql innoDB被设置为不可重复读级别时,默认一个事务只会创建一次读视图。
4.MVCC解决幻读
参考该博客:https://www.cnblogs.com/xuwc/p/13873293.html
如果一个事务中都是快照读,这样使用的读视图不变化,那么不会有幻读问题(新提交的记录不会对正在进行的事务产生影响)。
如果一个事务中涉及了现在读(如进行了update操作),那么可能产生幻读问题。
Q:为何不会出现不可重复读?
A:一个解释是:当事务A进行现在读相关操作(update等)时,会使用行锁避免其他事务进行数据更新。
解决幻读的方式
对于快照读,限制事务只能读取特定版本号之前的记录。
对于当前读,使用next key lock,阻止其他事务插入或更新数据。