浅读openGauss MVCC可见性判断机制
在数据的并发读写过程中,由于写入并不是原子性的,因此当一个线程正在写时,如果另一个线程进行读操作的话就很有可能产生数据不一致的问题。 比如数据的前半部分写入了,但是后半部分尚未写入,那么在读取时就会取到中间值,也就是脏数据,典型案例就是 64 位整型的写入将会分为两次写入。
解决这个问题的最简单方式就是使用读写锁,多个线程可以并发的读,不可并发地读写。但是对于数据库这类应用来说。对读写的并发有着更高的要求,因为通常而言应用都是读多写少,并且写入的代价是读取代价的几倍之多,一旦有数据写入并阻塞读取时,可能会导致较高的延迟,因此就有了多版本并发控制,它使得读写可以并发进行,读取不会阻塞写入,同时写入也不会阻塞读取。
一. 概述
多版本并发控制(Multi-Version Concurrency Control, MVCC)是一种通过冗余多份历史数据来达到并发读写目的的一种技术,在写入数据时,旧版本的历史数据将不会被删除,那么此时并发的读仍然能够读取到对应的历史数据,这样就使得读和写能够并发运行,并且不会出现数据不一致的问题。
在实现 MVCC 时,主要有两种方式:
在写入数据时将旧数据迁移到另一个地方,比如回滚段(undo log)。其他线程在读取改行数据时,从回滚段中将旧数据读出来。
另一种方式直接将新数据插入到相关表页中,在同一个存储区域中保存数据的多个版本,openGauss用的便是这一方式。
二、基本概念
事务ID
多版本并发控制既然会保留一份数据的多个版本,那么就需要能够区分出哪个版本是最新的,哪个版本是最旧的。一个最朴素的想法就是给每一个版本添加一个时间戳,用时间戳来比较新旧,但是时间戳不稳定,万一有人修改了服务器的配置,事情就乱套了。因此,openGauss使用了一个 32 位无符号自增整数来作为事务标识以比较新旧程度。
openGauss=# select txid_current();
txid_current
--------------
507
(1 row)
SnapshotData核心代码说明
typedef struct SnapshotData {
SnapshotType snapshot_type; * type of snapshot */
<br>
TransactionId xmin; /* all XID < xmin are visible to me */
TransactionId xmax; /* all XID >= xmax are invisible to me */
<br>
/*
* 正在运行的事务 txid 列表
* note: all ids in xip[] satisfy xmin t_infomask & HEAP_XMAX_INVALID) /* 未被当前事务删除 */
return true;
}
/* 该元组在进行中,并且插入语句不由当前事务执行,则不可见 */
return false;
}