注册
多版本并发控制MVCC
专栏/技术分享/ 文章详情 /

多版本并发控制MVCC

Arno 2025/09/19 154 0 0
摘要

1. 概念
通过保存数据在某个时间点的快照来实现并发控制的。也就是说,不管事务执行多长时间,事务内部看到的数据是不受其它事务影响的,根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。
与传统锁机制的区别:
悲观并发控制(锁):默认认为冲突会发生,因此通过加锁来预防。读会阻塞写,写会阻塞读。
乐观并发控制(MVCC):默认认为冲突不常发生。写操作会创建数据的新版本,而读操作去访问旧版本。因此,读操作永远不会阻塞写操作,写操作也永远不会阻塞读操作,极大提升了并发性能。
2.DM物理记录结构
为了适应多版本机制,高效地获取历史记录,每一条物理记录中包含了两个字段:TID和 RPTR。TID 保存修改记录的事务号,RPTR 保存回滚段中上一个版本回滚记录的物理地址。插入、删除和更新物理记录时。RPTR 指向操作生成的回滚记录的物理地址。物理记录格式如下:
image.png
3.MVCC的好处
数据库并发场景有三种,分别为:
读-读:不存在任何问题,也不需要并发控制
读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。所以MVCC可以为数据库解决以下问题
A.在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能;
B.同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题。
4.MVCC的两种模式
达梦数据库为用户提供了两种MVCC模式,通过参数TRX_VIEW_MODE控制:
1.基于回滚记录的MVCC(TRX_VIEW_MODE=0):
数据行包含事务TID和版本指针RPTR,使得记录间单向连接,形成链式版本结构。
事务根据当前活动事务的视图,依据链式版本结构,构成可见的记录集合[]。
2.基于时间戳的MVCC(TRX_VIEW_MODE=1):
全局维护一个大数组CMTARR(Commit Array),长度由参数TRX_CMTARR_SIZE控制(单位:百万)。事务在启动时获取一个时间戳SNAP_CMTSEQ,提交时将CMTARR中对应位置(事务号)的值设置为当前时间。数据版本的可见性通过比较SNAP_CMTSEQ和CMTSEQ来判断:如果SNAP_CMTSEQ >= CMTSEQ,则数据版本可见;否则不可见。
基于时间戳的MVCC是达梦数据库当前版本的默认模式,它提供了更高的性能和更低的内存开销。
5.MVCC的可见性判断规则
实现达梦MVCC的关键是可见性判断,找到对当前事务可见的特定版本数据。达梦使用以下规则进行可见性判断:
A.物理记录的TRXID等于当前事务号,说明是本事务修改的物理记录,物理记录可见。
B.物理记录的TRXID不在活动事务表中,并且TRXID小于NEXT_TID(下一个事务号),物理记录可见。
C.物理记录的TRXID包含在活动事务表中,或者TRXID大于等于NEXT_TID,物理记录不可见。
如果当前物理记录不可见,则按照该记录的ROLLPTR指示,找到其上一个版本,并再次应用上述规则进行可见性判断,直到找到可见版本为止。
对于基于时间戳的MVCC模式,可见性判断更为简单,只需要比较事务的时间戳即可,这大大提高了判断效率。
6.MVCC与事务隔离级别的集成
DM支持三种事务隔离级别:读未提交、读提交和串行化。默认是读提交级别。
A.读未提交(Read Uncommitted):在该级别下,事务可以读取其他事务尚未提交的数据,可能导致脏读问题。多次读取同一数据可能得到不同的结果。
B.读已提交(Read Committed):在该级别下,事务只能读取其他事务已经提交的数据。DM在执行每个SQL语句前收集一次活动事务信息,因此同一事务中的不同查询可能看到不同的数据状态。
C.串行化(Serializable):在该级别下,数据库管理系统会严格控制事务的执行顺序,通过加锁来实现。如果一个事务正在修改数据,其他试图读取或修改同一数据的事务将被阻塞,直到第一个事务完成。在事务启动时收集一次活动事务信息,确保同一事务中的所有查询看到一致的数据状态。
当使用串行化隔离级别时,如果一个串行化事务试图更新或删除数据,而这些数据在此事务开始后被其他事务修改并提交时,将报"串行化事务被打断"错误。
7.MVCC 的实现原理
MVCC的实现依赖于三个核心组件:数据的隐藏列 、回滚日志(Undo Log) 和ReadView(读视图)。三者协同工作,使得事务能够访问到符合其隔离级别的数据版本。
7.1数据的隐藏列
InnoDB存储引擎会为表中的每一行数据添加三个隐藏列,用于记录版本信息和事务相关数据:
DB_TRX_ID :记录最后一次修改该行数据的事务ID(6字节)。每次事务对行数据执行INSERT/UPDATE/DELETE操作时,都会将当前事务的ID写入该列。
DB_ROLL_PTR:回滚指针(7字节),指向该行数据的回滚日志(Undo Log),通过该指针可以找到数据的上一个版本。
DB_ROW_ID:行ID(6字节),当表没有主键或唯一索引时,InnoDB会用该列生成聚簇索引,确保每行数据有唯一标识。
7.2 回滚日志(Undo Log)
回滚日志是MVCC实现多版本的基础,用于保存数据被修改前的旧版本。
作用 :当事务需要回滚时,通过Undo Log恢复数据到修改前的状态(支持事务的原子性);
为MVCC提供数据的历史版本,供其他事务读取(支持并发读写)。
生成时机 :当事务执行INSERT/UPDATE/DELETE时,InnoDB会先将数据的旧版本写入Undo Log,再修改实际数据。
INSERT:Undo Log记录新插入的行信息,事务回滚时直接删除该行;
UPDATE/DELETE:Undo Log记录修改前的行数据,事务回滚时通过回滚指针恢复旧版本。
版本链 :多次修改同一行数据时,Undo Log会形成一条"版本链":每次修改后,新数据的DB_ROLL_PTR指向旧版本的Undo Log,旧版本的DB_ROLL_PTR再指向更早的版本,直至最初版本。
7.3 ReadView(读视图)
ReadView是事务在读取数据时生成的一个"快照",用于判断当前事务能看到哪些版本的数据。它本质上是一组用于过滤数据版本的规则,包含四个核心参数:
m_ids:当前活跃(未提交)的事务ID列表。
min_trx_id:活跃事务中最小的事务ID。
max_trx_id:系统为下一个事务分配的ID(即当前最大事务ID+1)。
creator_trx_id:生成该ReadView的事务ID。
7.4版本可见性判断规则
事务读取数据时,会根据ReadView的参数,对数据的DB_TRX_ID(最后修改事务ID)进行判断,决定是否可见:
若DB_TRX_ID == creator_trx_id:数据是当前事务自己修改的,可见。
若DB_TRX_ID < min_trx_id:修改该数据的事务在当前事务启动前已提交,可见。
若DB_TRX_ID > max_trx_id:修改该数据的事务在当前事务启动后才开始,不可见(需通过回滚指针找更早版本)。
若min_trx_id ≤ DB_TRX_ID ≤ max_trx_id:
若DB_TRX_ID在m_ids中(事务仍活跃):不可见(需找更早版本);
若DB_TRX_ID不在m_ids中(事务已提交):可见。
如果当前版本不可见,事务会通过DB_ROLL_PTR沿着Undo Log的版本链向上查找,直到找到符合规则的可见版本。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服