1.cmin和cmax是什么
PG中每个表都包含了一些系统字段,其中包括cmin和cmax。cmin:插入该元组的命令在插入事务中的命令标识(从0开始累加)
cmax:删除该元组的命令在插入事务中的命令标识(从0开始累加)
可以在Select命令的输出列表中显式地指定系统字段。
点击(此处)折叠或打开
- postgres=# create table tb1(c1 int);
- CREATE TABLE
- postgres=# begin;
- BEGIN
- postgres=# insert into tb1 values(1);
- INSERT 0 1
- postgres=# insert into tb1 values(2);
- INSERT 0 1
- postgres=# select xmin,xmax,cmin,cmax,* from tb1;
- xmin | xmax | cmin | cmax | c1
- ------+------+------+------+----
- 1055 | 0 | 0 | 0 | 1
- 1055 | 0 | 1 | 1 | 2
- (2 行记录)
- postgres=# rollback;
- ROLLBACK
2.cmin和cmax的作用
cmin和cmax用于
判断同一个事务内的其他命令导致的行版本变更是否可见。如果一个事务内的所有命令严格顺序执行,那么每个命令总能看到之前该事务内的所有变更,不需要使用命令标识。然而一个事务内存在命令交替执行的情况,比如使用游标进行查询。Fetch游标时看到的是声明游标时的数据快照而不是Fetch执行时,即声明游标后对数据的变更对该游标不可见。
Source code comment in src/backend/utils/time/tqual.c:
src/include/access/htup.h:
点击( 此处)折叠或打开
当xmax为0,即行版本还没有被删除时,t_cid代表插入命令的命令标识。
当xmax不为0,且插入事务标识xmin和删除事务标识xmax不同时,t_cid代表删除命令的命令标识。
当xmax不为0,且插入事务标识xmin和删除事务标识xmax相同时,t_cid代表组合命令标识。在backend的私有空间存储了组合命令标识到实际的{cmin,cmax}组合的映射。
执行VACUUM FULL时不需要cmin和cmax,所以t_xvac可以和t_cid共用一个存储空间。
1)每个命令使用事务内全局的命令标识计数器的当前值作为命令标识。
2)事务开始时,命令标识计数器被置为初值0
3)执行更新性的SQL(包括insert,update,delete,select ... for update等)时,在SQL执行后命令标识计数器增1
4)当命令标识计数器经过不断累加又回到初值0时,报错"cannot have more than 2^32-1 commands in a transaction"
由此可得出如下结论
1)非更新性的SQL和其后的第一个更新性SQL的 命令标识相同
2)同一个事务内的插入或删除行为对当前命令有效的条件是 cmin(或cmax) 命令标识。
这就可以解释[3.行版本可见性的判断]中, 命令标识比较时,有时带=,有时不带。
点击(此处)折叠或打开
- postgres=# begin;
- BEGIN
- postgres=# insert into tb1(c1) values(1);
- INSERT 0 1
- postgres=# declare tb1_v cursor for select xmin,xmax,cmin,cmax,* from tb1;
- DECLARE CURSOR
- postgres=# update tb1 set c1=2;
- UPDATE 1
- postgres=# select xmin,xmax,cmin,cmax,* from tb1;
- xmin | xmax | cmin | cmax | c1
- ------+------+------+------+----
- 1060 | 0 | 1 | 1 | 2
- (1 行记录)
-
-
- postgres=# fetch all from tb1_v;
- xmin | xmax | cmin | cmax | c1
- ------+------+------+------+----
- 1060 | 1060 | 0 | 0 | 1
- (1 行记录)
-
-
- postgres=# rollback;
- ROLLBACK
3.行版本可见性的判断
行版本可见性的完整判断逻辑课参考一下下面的代码注释Source code comment in src/backend/utils/time/tqual.c:
点击(此处)折叠或打开
- * ((Xmin == my-transaction && inserted by the current transaction
- * Cmin my-command && before this command, and
- * (Xmax is null || the row has not been deleted, or
- * (Xmax == my-transaction && it was deleted by the current transaction
- * Cmax >= my-command))) but not before this command,
- * || or
- * (Xmin is committed && the row was inserted by a committed transaction, and
- * (Xmax is null || the row has not been deleted, or
- * (Xmax == my-transaction && the row is being deleted by this transaction
- * Cmax >= my-command) || but it's not deleted "yet", or
- * (Xmax != my-transaction && the row was deleted by another transaction
- * Xmax is not committed)))) that has not been committed
4.cmin和cmax的内部存储
出于减少系统字段大小的考虑,cmin和cmax在行版本的头部使用同一个字段t_cid存储,并和t_xvac交叠。所以通过系统字段看到的cmin和cmax的值总是相同的。src/include/access/htup.h:
点击( 此处)折叠或打开
- typedef struct HeapTupleFields
- {
- TransactionId t_xmin; /* inserting xact ID */
- TransactionId t_xmax; /* deleting or locking xact ID */
-
- union
- {
- CommandId t_cid; /* inserting or deleting command ID, or both */
- TransactionId t_xvac; /* old-style VACUUM FULL xact ID */
- } t_field3;
- } HeapTupleFields;
当xmax为0,即行版本还没有被删除时,t_cid代表插入命令的命令标识。
当xmax不为0,且插入事务标识xmin和删除事务标识xmax不同时,t_cid代表删除命令的命令标识。
当xmax不为0,且插入事务标识xmin和删除事务标识xmax相同时,t_cid代表组合命令标识。在backend的私有空间存储了组合命令标识到实际的{cmin,cmax}组合的映射。
执行VACUUM FULL时不需要cmin和cmax,所以t_xvac可以和t_cid共用一个存储空间。
5.命令标识的分配
我们注意到不是每次执行 命令,都会导致命令标识增加。 命令标识的分配规则如下:1)每个命令使用事务内全局的命令标识计数器的当前值作为命令标识。
2)事务开始时,命令标识计数器被置为初值0
3)执行更新性的SQL(包括insert,update,delete,select ... for update等)时,在SQL执行后命令标识计数器增1
4)当命令标识计数器经过不断累加又回到初值0时,报错"cannot have more than 2^32-1 commands in a transaction"
由此可得出如下结论
1)非更新性的SQL和其后的第一个更新性SQL的 命令标识相同
2)同一个事务内的插入或删除行为对当前命令有效的条件是 cmin(或cmax) 命令标识。
这就可以解释[3.行版本可见性的判断]中, 命令标识比较时,有时带=,有时不带。