PostgreSQL技术大讲堂 - 第21讲:行可见性规则
PostgreSQL从小白到专家,是从入门逐渐能力提升的一个系列教程,内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容,希望对热爱PG、学习PG的同学们有帮助,欢迎持续关注CUUG PG技术大讲堂。
第21讲:行可见性规则
内容1:PostgreSQL事务id介绍
内容2:PostgreSQL DML操作原理
内容3:事务快照在可见性规则中的作用
内容4:T_xmin状态对于可见性规则判断的重要度
内容5:常见的行可见性规则的介绍
内容6:实现闪回功能
TXID介绍
· 事务id(txid)
当一个事务开始时,PostgreSQL中的事务管理系统会为该事务分配一个唯一标识符,即事务ID(txid).PostgreSQL中的txid被定义为一个32位的无符号整数,也就是说,它能记录大约42亿个事务。通常txid对我们是透明的,但是我们可以利用PostgreSQL内部的函数来获取当前事务的txid。
事务ID用来标识一个事务的先后顺序,该顺序决定了锁申请的优先权,已经访问一张表时对行的可见性规则判断。
testdb=# SELECT txid_current();
txid_current
--------------
100
(1 row)
Tuples Structure
· 元组(行)结构
t_xmin保存插入此元组的事务的txid,它的状态是行可见性判断关键的依据。
t_xmax保存删除或更新此元组的事务的txid。如果此元组未被删除或更新,则t_xmax设置为0,这意味着无效,它的状态也是行可见性判断关键的依据。
DML操作原理
· Insertion
· Deletion
· Update
执行第一个更新命令时,通过将txid 100设置为t_xmax,逻辑上删除Tuple_1,然后插入Tuple_2。然后,将元组1的t_ctid重写为指向元组2。
当执行第二个UPDATE命令时,与第一个UPDATE命令一样,Tuple_2在逻辑上被删除,Tuple_3被插入。
事务状态
· 四种事务状态
IN_PROGRESS
COMMITTED
ABORTED
SUB_COMMITTED
Commit Log
· 事务状态记录方式
事务快照
· 事务快照概述
事务快照是一个数据集,用于存储有关单个事务在某个时间点上是否所有事务都处于活动状态的信息。在这里,活动事务表示它正在进行或尚未启动。txid_current_snapshot的文本表示为“xmin:xmax:xip_list”,组件描述如下:
Xmin:最早仍在活动的txid。所有以前的事务要么提交并可见,要么回滚并停止。
Xmax:第一个尚未分配的txid。截至快照时,所有大于或等于此值的txid尚未启动,此不可见。
xip_list:快照时的活动txid。该列表仅包含xmin和xmax之间的活动txid。
testdb=# SELECT txid_current_snapshot();
txid_current_snapshot
-----------------------
100:104:100,102
(1 row)
例如,在快照'100:104:100,102'中,xmin是'100',xmax是'104',xip_list是'100,102'。
可见性规则世界观
· 事务快照在可见性规则中的意义
富有哲理性的判断规则:过去发生过的为可见,将来未发生的为不可见。
行可见性判断重要因素
· 可见性判断的重要因素
可见性检查规则是一组规则,关键的判断因素有:t_xmin、t_xmax、clog和获取的事务快照确定每个元组是否可见。
T_xmin的三种状态ABORTED、IN_PROGRESS、COMMITTED是判断的第一前提条件。
ABORTED状态
· t_xmin =ABORTED
t_xmin =ABORTED,则判断此行不可见
/* t_xmin status = ABORTED */
Rule 1: IF t_xmin status is 'ABORTED' THEN
RETURN 'Invisible'
END IF
IN_PROGRESS状态
· t_xmin=IN_PROGRESS
t_xmin=IN_PROGRESS,当前事务可见,其它事务不可见
/* t_xmin status = IN_PROGRESS */
IF t_xmin status is 'IN_PROGRESS' THEN
IF t_xmin = current_txid THEN
Rule 2: IF t_xmax = INVALID THEN
RETURN 'Visible'
Rule 3: ELSE /* this tuple has been deleted or updated by the current transaction itself. */
RETURN 'Invisible'
END IF
Rule 4: ELSE /* t_xmin ≠ current_txid */
RETURN 'Invisible'
END IF
END IF
COMMITTED状态
· t_xmin=COMMITTED
t_xmin=COMMITTED,此状态判断时还得看t_xmax的值,如果t_xmax的值为0,则此行可见;如果不为0,那么判断时还得看t_xmax的状态是当前事务还是非当前事务,判断规则就比较复杂。
/* t_xmin status = COMMITTED */
IF t_xmin status is 'COMMITTED' THEN
Rule 5: IF t_xmin is active in the obtained transaction snapshot THEN
RETURN 'Invisible'
Rule 6: ELSE IF t_xmax = INVALID OR status of t_xmax is 'ABORTED' THEN
RETURN 'Visible'
ELSE IF t_xmax status is 'IN_PROGRESS' THEN
Rule 7: IF t_xmax = current_txid THEN
RETURN 'Invisible'
Rule 8: ELSE /* t_xmax ≠ current_txid */
RETURN 'Visible'
END IF
ELSE IF t_xmax status is 'COMMITTED' THEN
Rule 9: IF t_xmax is active in the obtained transaction snapshot THEN
RETURN 'Visible'
Rule 10: ELSE
RETURN 'Invisible'
END IF
END IF
END IF
可见性判断概述
· 可见性判断示例
R6判断规则
· T3 时根据规则6进行判断
Rule6(Tuple_1)⇒Status(t_xmin:199) = COMMITTED ∧ t_xmax = INVALID ⇒Visible
T_xmin=commit,并且t_xman=0,该行对于所有的事务均可见
R7与R2判断规则
· T5时事务ID为200的根据规则7、2进行判断
Rule7(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) = IN_PROGRESS ∧ t_xmax:200 = current_txid:200 ⇒ Invisible
Rule2(Tuple_2): Status(t_xmin:200) = IN_PROGRESS ∧ t_xmin:200 = current_txid:200 ∧ t_xmax = INVAILD ⇒ Visible
此时块中包含两行数据,对于事务id=200来说,它的判断规则是:
第一行数据根据规则7判断,t_xmin=commit,同时(t_xmax=200)= IN_PROGRESS,并且t_xmax:200为当前事务id,则第一行判断为不可见。
第二行根据规则2判断, t_xmin=commit,同时(t_xmax=200)为当前事务id,并且t_xmax为无效,则该行可见。
testdb=# -- txid 200
testdb=# SELECT * FROM tbl;
name
------
Hyde
R8与R4判断规则
· T5时事务ID为201的根据规则8、4进行判断
Rule8(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) = IN_PROGRESS ∧ t_xmax:200 ≠ current_txid:201 ⇒ Visible
Rule4(Tuple_2): Status(t_xmin:200) = IN_PROGRESS ∧ t_xmin:200 ≠ current_txid:201 ⇒ Invisible
此时块中包含两行数据,对于事务id=201来说,它的判断规则是:
第一行数据根据规则8判断,t_xmin=commit,同时(t_xmax=200)= IN_PROGRESS,并且t_xmax:200不是当前事务id,则第一行判断为可见。
第二行根据规则2判断, (t_xmax=200)状态为IN_PROGRESS,同时t_xmin不是当前事务id,则该行不可见。
testdb=# -- txid 201
testdb=# SELECT * FROM tbl;
name
--------
Jekyll
R10与R6判断规则
· T7时事务ID为201的根据规则10、6进行判断(READ COMMITED)
Rule10(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) = COMMITTED ∧ Snapshot(t_xmax:200) ≠ active ⇒ Invisible
Rule6(Tuple_2): Status(t_xmin:200) = COMMITTED ∧ t_xmax = INVALID ⇒ Visible
T7时事务id为200的提交了事务,对于事务id=201来说,它的判断规则是:
第一行根据规则10判断,t_xmin=commit,同时(t_xmax=200)= COMMITTED ,并且Snapshot(t_xmax:200) 状态为非活动,则第一行判断为不可见。
第二行根据规则6判断, (t_xmax=200)状态为COMMITTED ,同时t_xmax为无效,则该行可见。
testdb=# -- txid 201 (READ COMMITTED)
testdb=# SELECT * FROM tbl;
name
------
Hyde
R9与R5判断规则
· T7时事务ID为201的根据规则9、5进行判断(REPEATABLE READ)
Rule9(Tuple_1): Status(t_xmin:199) = COMMITTED ∧ Status(t_xmax:200) = COMMITTED ∧ Snapshot(t_xmax:200) = active ⇒ Visible
Rule5(Tuple_2): Status(t_xmin:200) = COMMITTED ∧ Snapshot(t_xmin:200) = active ⇒ Invisible
如果事务的隔离级别是可重复读,那么其判断规则就会发生变化,T7时事务id为200的提交了事务,对于事务id=201来说,它的判断规则是:
第一行根据规则9判断,t_xmin=commit,同时(t_xmax=200)= COMMITTED ,并且Snapshot(t_xmax:200) 状态为活动,则第一行判断为可见。
第二行根据规则5判断, t_xmax=200状态为COMMITTED , Snapshot(t_xmin:200) 为活动,则该行不可见,通过该规则,不会导致幻读发生。
testdb=# -- txid 201 (REPEATABLE READ)
testdb=# SELECT * FROM tbl;
name
--------
Jekyll
提高判断效率
· Hint Bits
由于进行行可见性判断时都要查看存储在clog中t_xmin和t_xmax的状态,为了解决对clog频繁访问这个问题,PostgreSQL使用了提示位,如下所示:
#define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */
#define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */
#define HEAP_XMAX_COMMITTED 0x0400 /* t_xmax committed */
#define HEAP_XMAX_INVALID 0x0800 /* t_xmax invalid/aborted */
实现闪回功能
PostgreSQL由于数据的更新时新旧数据都保留在数据块中,那么如果要实现像Oracle一样的闪回查询功能应该是可以实现的,只要在判断时先判断该查询是否是闪回查询,然后再根据一个针对闪回查询的可见性规则判断就可以实现。
如果实现闪回查询,那么涉及到Vacuum操作时需要考虑更多的因素,需要有一个参数来设置块中被删除的行保留的时间长度。
以上就是【PostgreSQL从小白到专家】第21讲 - 行可见性规则 的内容,欢迎一起探讨交流,往期视频及文档,联系CUUG