PHP实战中知识总结 / PgSQL - 元组结构-Tuple Structure

一、元组结构

元组由三部分组成,即HeapTupleHeaderData结构、空位图和用户数据。

(1)t_xmin:保存插入(insert)该元组的事务的txid。

(2)t_xmax:保存删除或更新此元组的事务的txid。如果此元组尚未被删除或更新,则t_xmax设置为0,这意味着无效。

(3)t_cid:保存命令id(cid)。这意味着在从0开始的当前事务中执行此命令之前执行了多少SQL命令。例如,假设我们在一个事务中执行三个INSERT命令:'BEGIN; INSERT; INSERT; INSERT; COMMIT;'。如果第一个命令插入这个元组,则t_cid设置为0。如果第二个命令插入此命令,则t_cid设置为1,依此类推。当这个元组被更新时,这个元组的t_ctid指向新的元组;否则,t_ctid会指向自己。

二、insert tuple

通过插入操作,一个新元组被直接插入到目标表的页面中

Tuple_1:

(1)t_xmin被设置为99,因为这个元组是由txid 99插入的。

(2)t_xmax设置为0,因为此元组尚未删除或更新。

(3)t_cid设置为0,因为此元组是txid 99插入的第一个元组。

(4)t_ctid设置为(0,1),它指向自身,因为这是最新的元组。

三、delete tuple

Tuple_1被 txid=111的事务删除

Tuple_1:

t_xmax设置为111。

如果提交了txid 111,则不再需要Tuple_1。在PostgreSQL中,不需要的元组通常被称为死元组。

四、update tuple

在更新操作中,PostgreSQL从逻辑上删除最新的元组并插入一个新元组。

假设txid 99插入的行被txid 100更新了两次。

当执行第一个更新命令时,通过将txid 100设置为t_xmax,逻辑上删除Tuple_1,然后插入Tuple_2。然后,将Tuple_1的t_ctid重写为指向Tuple_2。Tuple_1和Tuple_2的头字段如下所示。

Tuple_1:

t_xmax设置为100。

t_ctid从(0,1)重写为(0,2)。

Tuple_2:

t_xmin设置为100。

t_xmax设置为0。

t_cid设置为0。

t_ctid设置为(0,2)。

当执行第二个更新命令时,与第一个更新命令一样,逻辑上删除Tuple_2,插入Tuple_3。Tuple_2和Tuple_3的头字段如下所示。与删除操作一样,如果提交了txid 100,则Tuple_1和Tuple_2将是死元组,如果中止了txid 100,则Tuple_2和Tuple_3将是死元组。

Tuple_2:

t_xmax设置为100。

t_ctid从(0,2)重写为(0,3)。

Tuple_3:

t_xmin设置为100。

t_xmax设置为0。

t_cid设置为1。

t_ctid设置为(0,3)。

五、通过pageinspect扩展查看tuple

// 安装pageinspect扩展
postgres=# CREATE EXTENSION pageinspect;
CREATE EXTENSION
// 建表并插入两条数据
postgres=# CREATE TABLE tbl (data text);
CREATE TABLE
postgres=# INSERT INTO tbl VALUES('A');
INSERT 0 1
postgres=# INSERT INTO tbl VALUES('B');
INSERT 0 1
// 可以查看到两条数据的元组头部和元组原始数据
postgres=# SELECT lp as tuple, t_xmin, t_xmax, t_field3 as t_cid, t_ctid FROM heap_page_items(get_raw_page('tbl', 0));
tuple | t_xmin | t_xmax | t_cid | t_ctid
-------+--------+--------+-------+--------
   1 |  493 |   0 |   0 | (0,1)
   2 |  494 |   0 |   0 | (0,2)
(2 rows)
// 删除 B 数据之后,B数据已被标识删除,dead tuple,待回收
postgres=# delete from tbl where data = 'B';
DELETE 1
postgres=# SELECT lp as tuple, t_xmin, t_xmax, t_field3 as t_cid, t_ctid FROM heap_page_items(get_raw_page('tbl', 0));
tuple | t_xmin | t_xmax | t_cid | t_ctid
-------+--------+--------+-------+--------
   1 |  493 |   0 |   0 | (0,1)
   2 |  494 |  495 |   0 | (0,2)
(2 rows)
//更新A数据。逻辑上删除Tuple_1,然后插入Tuple_3
postgres=# update tbl set data = 'C' where data = 'A';
UPDATE 1
postgres=# SELECT lp as tuple, t_xmin, t_xmax, t_field3 as t_cid, t_ctid FROM heap_page_items(get_raw_page('tbl', 0));
tuple | t_xmin | t_xmax | t_cid | t_ctid
-------+--------+--------+-------+--------
   1 |  493 |  496 |   0 | (0,3)
   2 |  494 |  495 |   0 | (0,2)
   3 |  496 |   0 |   0 | (0,3)
(3 rows)
// 手动vacuum进行垃圾回收
postgres=# vacuum;
VACUUM
postgres=# SELECT lp as tuple, t_xmin, t_xmax, t_field3 as t_cid, t_ctid FROM heap_page_items(get_raw_page('tbl', 0));
tuple | t_xmin | t_xmax | t_cid | t_ctid
-------+--------+--------+-------+--------
   1 |    |    |    |    // line pointers不会被回收
   2 |    |    |    |    // 会产生空间碎片
   3 |  496 |   0 |   0 | (0,3)
(3 rows)
// 全量垃圾回收,彻底回收,不会有空间碎片,重写表
postgres=# vacuum full;
VACUUM
postgres=# SELECT lp as tuple, t_xmin, t_xmax, t_field3 as t_cid, t_ctid FROM heap_page_items(get_raw_page('tbl', 0));
tuple | t_xmin | t_xmax | t_cid | t_ctid
-------+--------+--------+-------+--------
   1 |  496 |   0 |   0 | (0,1)
(1 row)

PHP实战中知识总结