MVCC,全称 Multi-Version Concurrency Control,即多版本并发控制。是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
MVCC可以维护一行数据的多个版本,做到即使有读写冲突也能不加锁。在数据库并发场景中无非就三种读读、读写、写写。读读不需要并发控制,写写冲突是采用锁的方式解决,达梦采用的就是悲观锁,读写冲突就是用MVCC来解决的。MVCC只是一个标准,没有具体的实现方法,不同的数据库有不同的实现方法。在DM数据库中是基于物理记录和回滚记录实现行级多版本支持,数据页中只保留物理记录的最新版本,通过回滚记录维护历史版本,所有事务针对特定的版本进行操作。
提高并发性能:读操作不加锁,所以读操作不会阻塞写操作,写操作也不会阻塞读操作,有效地提高数据库的并发性能。
降低死锁风险:由于无需使用显式锁来进行并发控制,MVCC可以降低死锁的风险。
1、MVCC只在READ COMMITTED(已读提交)这隔离级别下适用,DM只支持数据库修改为读提交和可串行化。
2、MVCC实现原理是由俩个隐藏字段、undo日志、读视图来实现的。
在DM中,除了一些行数据之外,还有一些隐藏的的字段,它们是在DM内部使用的,默认情况下是不会显示给用户查看的,如果想看可以额外指定列ROWID,TRXID。
select *, ROWID,TRXID from TEST.USERS ;
以下是DM数据库的三个隐藏字段,分别是:TRXID、RPTR和ROWID。隐藏字段TRXID、RPTR用于实现MVCC,ROWID是表中没有主键或唯一索引时,索会自动按ROWID来产生聚集索引树来存储数据。
Undo 日志是 MVCC 能够得以实现的核心所在。不同的事务或者相同的事务对同一个记录做修改,undo log会成为一条记录版本的线性表,也就是事务链,链表的头部是最新的旧记录,链表的尾部是最早的旧记录,
事务1:insert into users(id,name) values (1,'张三');
因为是第一条记录它没有上一个版本,所以回滚指针RPTR是NULL。
事务2:update users set name = '李四' where id =1;
接下来我们开启第二个事务,将张三改为李四。在事务2修改该行数据时,数据库会对该事务号TID=1加排它锁,在达梦中是采用TID锁进行锁事务号来代替行锁。然后把该行数据拷贝到 undo log 中作为旧记录,即在 undo log 中有当前行的拷贝副本,并将回滚指针指向拷贝到 undo log 的副本记录,即表示我的上一个版本就是它,事务提交后,释放锁。如下图,张三的记录存在undo log中。
此时,我们开启一个事务3,将李四改为王五,修改该行数据时,跟上面一样数据库也先将该事务行加TID锁,然后把该行数据拷贝到 undo log 中,作为旧记录,发现该行记录已经有 undo log 了,那么最新的旧数据李四作为链表的表头,插在该行记录的 undo log 最前面,最早的旧数据张三做链表的尾部,如下图:
事务3:update users set name = '王五' where id =1;
通过 RPTR 和 Undo日志 的配合,DM能够有效地管理事务的一致性和隔离性,从而提供了高并发环境下的读取一致性和事务隔离性。
在DM中,判断事务的可见性是通过读视图来实现的。事务进行快照读操作的时候生产的一个读视图,在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID。它主要包含三个字段:TRX_ID、NEXTID和MIN_ACTIVE_ID。
1、首先比较TRX_ID < MIN_ACTIVE_ID, 如果小于,那肯定是已经提交了,当前事务能看到这个TRX_ID 所在的记录,如果大于等于进入下一个判断
2、接下来判断 TRX_ID 是否大于等于 NEXTID, 如果大于等于则代表TRX_ID 所在的记录是在事务开始生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断。
3、最后判断TRX_ID 是否在活跃事务之中,如果在并且等于当前事务号,说明是本事务修改的记录,那么该记录肯定可见;如果在并且不等于当前事务号,则代表该事务生成时刻,其他事务还在活跃,还没有Commit,那么其修改的数据,我当前事务是看不见的;如果不在活跃事务列表中,则说明,在事务生成之前就已经Commit了,你修改的结果,我当前事务是能看见的。
MVCC在高并发下起到至关重要的作用,理解MVCC的原理是开发和DBA必不可少的知识点,希望通过本文能够让读者加深对MVCC的理解。
达梦社区技术https://eco.dameng.com




