注册
事务隔离级别与MVCC
技术分享/ 文章详情 /

事务隔离级别与MVCC

Phi Sagittarii Merak 2026/05/21 98 1 0

DM8事务隔离级别与MVCC

事务隔离级别

读未提交

读未提交隔离级别是最不严格的隔离级别,即允许事务读取另一个事务未提交的数据。在此隔离级别下性能最好,但由于脏读问题,在生产环境中几乎不使用。示例如下。

开启两个会话,会话1中设置隔离级别为READ UNCOMMITTED,向表中插入一条数据并且不提交
image.png

在会话2中,查询测试表数据,在默认事务隔离级别中,测试表仅有一条数据。设置隔离级别为READ UNCOMMITTED后,会话2中可以查询到会话1尚未提交的数据
image.png

读已提交

读已提交允许事务读取另一个事务已提交的数据,在此隔离级别下避免了脏读问题,能够满足大多数应用的要求,并最大限度的保证系统并发性能,但仍然会出现不可重复读和幻读的问题,示例如下:

开启两个会话,会话1中设置隔离级别为READ COMMITTED,向表中插入一条数据并提交
image.png

会话2中先后执行两次select查询数据,第二次查询时,会话2中可以查询到会话1已提交的数据。

image.png

可重复读

可重复读可以保证在一个事务内,多次读取同一数据记录时,结果一样。DM不支持该隔离级别。在postgresql中,通过对每一个事务创建快照(snapshot)实现可重复读。具体来说,postgresql是为每一个事务创建一个快照并冻结,之后该事务的所有操作都基于这份快照,从而实现隔离。快照中会存储以下三种数据:xmin:所有活跃事务中最小的事务ID,xmax:第一个尚未分配的事务ID,xip_list:当前活跃事务ID列表。之后利用行版本信息对比,由此判断可见性。与postgresql读已提交隔离级别的区别是,可重复读隔离级别只在事务开始时创建一次快照,因此只能看到事务开始前已提交的数据,以下示例使用postgresql-12。

在会话1中,设置事务隔离级别为可重复读,在会话2中,向测试表插入数据,插入数据后,会话1中查询结果不变

image.png

image.png

串行化

在串行化隔离级别下,可以消除不可重复读和幻读的问题。让并发事务的执行结果与串行执行完全相同,但该隔离级别对性能影响巨大,几乎不会采用。此外,在该隔离级别下,如果某个事务试图修改自身事务开始后,被其他事务修改并提交的数据,则会触发串行化失败,示例如下:

开启两个会话,会话1中对id为1的数据进行更新但不提交
image.png

在会话2中,同样对id为1的数据进行更新,由于此时会话1尚未提交,因此时会话2的操作会被阻塞

image.png

提交会话1的操作,此时会话2会出现串行化失败,因为会话2中的事务试图修改被会话1修改并已提交的数据

MVCC

在多版本控制以前,数据库仅通过锁机制来实现并发控制。数据库对读操作上共享锁,写操作上排他锁。但在这种情况下,出现读写冲突时就会产生阻塞,极大地影响了数据库并发性能。DM的MVCC通过实现行级多版本,消除了行锁。读写操作不会相互阻塞,极大地提高了数据库并发性能。

DM8提供了两种判断事务可见性的模式,使用参数TRX_VIEW_MODE控制,分别如下:

基于回滚记录的MVCC(TRX_VIEW_MODE=0):
数据行包含事务TID和版本指针RPTR,使得记录间单向连接,形成链式版本结构

基于时间戳的MVCC(默认,TRX_VIEW_MODE=1):
全局维护一个大数组CMTARR(Commit Array),长度由参数TRX_CMTARR_SIZE控制(单位:百万)。事务在启动时获取一个时间戳SNAP_CMTSEQ,提交时将CMTARR中对应位置(事务号)的值设置为当前时间。数据版本的可见性通过比较SNAP_CMTSEQ和CMTSEQ来判断:如果SNAP_CMTSEQ >= CMTSEQ,则数据版本可见;否则不可见。

基于回滚记录的MVCC

DM 数据库基于物理记录和回滚记录实现行级多版本支持,数据页中只保留物理记录的最新版本,通过回滚记录维护历史版本,每一条物理记录中包含了两个字段:TID 和 RPTR。

TID(本行数据的事务版本): 记录最近更新或插入这条记录的事务ID,可以理解为最后修改该行数据的事务号

RPTR(回滚指针,指向上一个版本):当事务回滚时,可以通过ROLPTR找到之前版本的数据。也可以在判断事务可见性时,通过该指针遍历到满足要求的版本

trxid隐藏字段可以被查询,示例如下:
image.png

事务可见性判断:

在基于回滚记录的MVCC中,通过活动事务视图来判断行可见性,主要使用以下几个记录

  • TID(事务ID):事务自身的事务号。
  • 活动事务表:当前系统中活跃事务号集合。
  • NEXT_TID:下一个将要分配的事务ID(即当前最大已分配事务ID + 1)。
  • 行物理记录TID:数据行的一个版本,其 TID 字段表示创建/修改该版本的事务ID。

对行可见性的判断遵循以下规则:

行TID = 当前事务号。说明这条记录是本事务自己修改,因此可见

行TID不在活动事务表内,并且小于NEXT_TID。说明创建该行的事务已经结束,因此可见

行TID包含在活动事务表内,或者TID>=NEXT_TID。说明创建该行的事务尚未结束,或者该行记录是在当前事务快照之后才创建的,因此不可见。

以上规则是主流的MVCC实现,但存在的问题是,当全局事务量较多时,由于每个事务均需要维护一个包含当前活跃事务的数据,代价太大。因此,DM还提供了以下第二种MVCC方式

基于时间戳的MVCC

这种MVCC的实现方式是,全局维护一个大数组CMTARR,在事务启动时,将这个数组中对应位置上的CMTSEQ置为0,事务提交时,则将这个位置上的CMTSEQ置为当前时间

之后事务或sql启动时,只需要获取当前时间戳,再与记录ID在CMTARR对应位置上的时间戳比对,就可以判断可见性了:

SNAP_CMTSEQ >= CMTSEQ 说明改行记录在事务之前提交,可见

SNAP_CMTSEQ < CMTSEQ说明改行记录在事务之后提交,不可见

CMTSEQ = 0,说明创建该行记录的事务尚未结束,不可见

这个大数组的长度,可以由静态参数TRX_CMTARR_SIZE控制,这个参数设置越大,内存消耗越多。因此此参数值设置应当考虑单位时间内事务完成数量

达梦社区技术https://eco.dameng.com

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服