Postgres_MVCC

postgres MVCC(mutil-version concurreny control)

综述: postgres使用mvcc并发控制来实现对数据的并发读写操作。并提供四大事务隔离级别供用户选择,默认为Repeatable read(可重复度), 根据隔离级别的处理逻辑,修改和查询Tuples也可能会有一定的差异

1: 四大事务隔离级别

  1. Read uncommited, 可以读取到其他事务未提交的变更
  2. Read commited(default), 只能读取到其他事务提交的变更, 同一个事务,读取的快照条件会变化
  3. Repeatable read , 一个事务中,读取到的数据不变,Read Snapshot, 只能读到该事务开始之前提交的数据(小于当前事务id的事务), 同一个事务,读取的快照条件不变
  4. Serializable , 串行化读,效率低, 数据强一致

1: Transaction ID
每个事务开始行的时候,postgres都会为其分配一个独一无二的id,递增,在事务运行的过程中可以通过 txid_current() 函数查看当前事务的事务id

1
2
begin
select txid_current(); --

postgres内置特殊的txid

1
2
3
0 Invalid sub txid   
1 Reinitialize within-transaction
2 Frozen txid

avatar

小于当前事务id的事务对当前事务可见,否则不可见。环形等价

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 数据Tuple结构
typedef struct HeapTupleFields
{
TransactionId t_xmin; /* inserting xact ID ,插入该Tuple事务id,*/
TransactionId t_xmax; /* deleting or locking xact ID. 删除或操作该Tuple的最大事务ID */

union
{
CommandId t_cid; /* inserting or deleting command ID, or both,操作Tuple命令数量 */
TransactionId t_xvac; /* old-style VACUUM FULL xact ID */
} t_field3;
} HeapTupleFields;


// 四大事务状态
#define TRANSACTION_STATUS_IN_PROGRESS 0x00 // 事务运行中
#define TRANSACTION_STATUS_COMMITTED 0x01 // 事务已经提交
#define TRANSACTION_STATUS_ABORTED 0x02 // 事务已经终止
#define TRANSACTION_STATUS_SUB_COMMITTED 0x03 // 提前提交,当前事务的父事务未提交或被终止

2:插入、删除、更新Tuples

1. Insertion
avatar

1
2
3
4
5
6
Operation: txid = 99执行插入操作
存储数据Tuple的Header数据解释
t_xmin = 99 // 数据插入的事务id为99
t_xmax = 0 // 该tuple没有被删除或更新
t_cid = 0 // 该tuple没有被其他的命令处理过, 被处理过就执行t_cid++
t_ctid = (0,1) // 该tuple被更新时,则指向新的tuple,当前tuple未被更新则指向本身

2. Deletion
删除操作为异步操作,先逻辑删除目前Tuple,然后由VACUUM后台线程来进行回收,该操作的txid被记录在t_xmax中

avatar

1
2
3
4
5
6
Operation: txid = 111执行删除操作操作
存储数据Tuple的Header数据解释
t_xmin = 100 // 数据插入的事务id为100
t_xmax = 0 // 该tuple被txid=111事务删除
t_ctid = (0,1) // 该tuple被更新时,则指向新的tuple,当前tuple未被更新则指向本身
当事务被提交完成之后,则Tuple_1就变成了dead tuples.会被VACUUM进程自动的移除所属page

3: Update
在执行update操作时候,pg会逻辑删除旧Tuple然后插入一个新的Tuple来达到更新操作
avatar

1
2
3
4
5
6
7
8
UPDATE tbl SET date = 'B'

Tuple_1 -> Tuple_2
t_ctid : 由(0,1)修改为(0,2)即指向最新的Tuple 相对page的offset, 即表示该Tuple(Tuple_1)不是最新的Tuple, Tuple_2表示该Update操作的插入行

UPDATE tbl SET date = 'C'

t_cid = 1 :

ps:如果该事务被committed则Tuple_1和Tuple_2就变化成Dead Tuple,如果该事务被Abouted则Tuple_2和Tuple_3就变化成Dead Tuple

3: Commit Log(clog)

avatar

事务时间线, 记录各时间线事务的状态。事务之间的可见依据

Transaction Snapshot
事务快照标志着当前事务对于那些事务是可读/不可读

函数 txid_current_snapshot()

1
2
3
4
5
6
7
8
# SELECT txid_current_snapshot();
txid_current_snapshot
-----------------------
100:104:100,102
// txid小于100的对于当前事务可见
// txid大于等于104的对于当前事务不可见
// txid在100-102之间的,若已经被
(1 row)

结构描述: xmin:xmax:xip_list

xmin: 最小的仍处理active状态的事务,小于xmin的都对当前事务课件
xmax: 尚未分配的txid,也就是最大的事务id所有大于或等于此txid的都不可见
xip_list: 在xmin-xmax区间中并且处于活动状态的事务

4: 事务可见检查策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Rules_1: t_xmin = ABOUTED // t_xmin 已丢弃
* IF Status(t_xmin) == "ABOUTED" ==> InVisible

// Rules_2: t_xmin = IN_PROGRESS // 运行中
switch {
case t_xmin != current_txid: return InVisible;
case t_xmin == current_txid && t_xmax == INVAILD: return Visible;
case t_xmin == current_txid && t_xmax != INVAILD: return InVisible; // 该Tuple被当前tx删除或者更新,所以对当前事务不可见
}

// Rules_3: t_xmin = Committed // t_xmin 已提交
switch {
case Snapshot(t_xmin) == active: return InVisible;

case t_xmax == INVAILD || t_xmax == ABOUTED: return Visble;

case Status(t_xmax) == IN_PROGRESS && t_xmax != current_txid: return Visble;

case Status(t_xmax) == IN_PROGRESS && t_xmax == current_txid: return InVisble;

case Status(t_xmax) == Committed && Snapshot(t_xmax) = active: return Visible;

case Status(t_xmax) == Committed && Snapshot(t_xmax) != active: return InVisible;
}

References
[1] interdb blog: http://www.interdb.jp/pg/pgsql05.html
[2] postgres source code: https://github.com/postgres/postgres