暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

ctid和其他PostgreSQL表内部

原创 eternity 2022-08-03
345

在参加会议时,讨论转向了系统专栏,尤其是ctid。这是在备份和数据在磁盘上的物理位置的环境中进行的。问题是,这是否可以用来确定是否需要增量备份,以及是否足以复制ctid所指向的字段。不完全是这样,但我想用这个问题更详细地描述系统列。

在PostgreSQL表中,可能会发现(取决于服务器版本)以下其他系统列:

  • tableoid

  • xmin

  • xmax

  • cmin

  • cmax

  • ctid

  • oid

大多数列存在于每个表中,但在执行SELECT*时被排除在外。但是,可以在查询中包含列,然后PostgreSQL也将返回列的值。

让我们详细介绍一下:

tableoid表格

tableoid是行最初来自的表中的OID。在一个简单的表中,这只是表本身的OID。

oid=# CREATE TABLE show_oid (id INT);
CREATE TABLE
oid=# SELECT 'public.show_oid'::regclass::oid;
  oid  
-------
 79824
(1 row)

oid=# INSERT INTO show_oid VALUES (1), (2);
INSERT 0 2
oid=# SELECT tableoid, id FROM show_oid;
 tableoid | id 
----------+----
    79824 |  1
    79824 |  2
(2 rows)
复制

所有行都来自oid=79824的表。

一旦使用分区,这种情况就会改变。在PostgreSQL中,分区是一个继承的表。简单地说,数据位于子表(分区)中,当查询父表时,PostgreSQL还会搜索所有子表中的数据。下面是一个分区示例。首先,我们创建一个父表,并按范围对其进行分区:

oid=# DROP TABLE IF EXISTS show_oid;
DROP TABLE
oid=# CREATE TABLE show_oid (id INT) PARTITION BY RANGE (id);
CREATE TABLE
oid=# CREATE TABLE show_oid_p1 PARTITION OF show_oid
oid-#    FOR VALUES FROM (1) TO (10);
CREATE TABLE
oid=# CREATE TABLE show_oid_p2 PARTITION OF show_oid
oid-#    FOR VALUES FROM (10) TO (20);
CREATE TABLE
oid=# CREATE TABLE show_oid_p3 PARTITION OF show_oid
oid-#    FOR VALUES FROM (20) TO (30);
CREATE TABLE
oid=# SELECT 'public.show_oid'::regclass::oid AS show_oid,
oid-#        'public.show_oid_p1'::regclass::oid AS show_oid_p1,
oid-#        'public.show_oid_p2'::regclass::oid AS show_oid_p2,
oid-#        'public.show_oid_p3'::regclass::oid AS show_oid_p3;
 show_oid | show_oid_p1 | show_oid_p2 | show_oid_p3 
----------+-------------+-------------+-------------
    79827 |       79830 |       79833 |       79836
(1 row)
复制

我们可以看到每个分区都有一个不同的oid。现在,让我们插入一些数据:

oid=# INSERT INTO show_oid VALUES (1), (2), (9), (10), (11), (19), (20), (21), (29);
INSERT 0 9
oid=# SELECT tableoid, id FROM show_oid ORDER BY tableoid, id;
 tableoid | id 
----------+----
    79830 |  1
    79830 |  2
    79830 |  9
    79833 | 10
    79833 | 11
    79833 | 19
    79836 | 20
    79836 | 21
    79836 | 29
(9 rows)

复制

即使数据是从父表中选择的(show_oid,oid=79827),也没有一行从该表返回数据。所有数据都来自其中一个分区。在插入数据期间,PostgreSQL根据分区键将数据移动到分区中。父表保持为空:

oid=# SELECT tableoid, id FROM ONLY show_oid ORDER BY tableoid, id;
 tableoid | id 
----------+----
(0 rows)
复制

这里唯一的关键字告诉PostgreSQL不要扫描任何继承的表。由于父表为空,因此不返回任何行。

xmin+xmax

xmin和xmax列包含事务ID。xmin显示行何时可见(所需的最小事务ID),xmax显示行何时不再可见(最大有效事务ID)。在像PostgreSQL这样的关系数据库中,可以同时有许多正在进行的事务。每个交易都有自己的交易ID,可以查询:

oid=# SELECT txid_current();
 txid_current 
--------------
         7034
(1 row)

oid=# SELECT txid_current();
 txid_current 
--------------
         7035
(1 row)
复制

上面的例子浪费了两个事务ID:PostgreSQL中的每个命令都包装在一个事务中。如果不启动事务,PostgreSQL将自动将每个命令包装在单个事务中。这就是为什么示例中的事务ID随着txid\u current()函数的每次调用而增加的原因。

回到我们的问题上来:每个事务都可以更改数据。事务隔离级别指定哪些事务可以看到哪些更改。一旦提交了数据,以后的每个事务都可以看到更改-这一部分很容易。对于正在进行的事务,事务ID指定哪些更改在哪一点可见。让我们来看一个例子。首先,我们需要一张桌子:

oid=# CREATE TABLE show_xmin_xmax (id INT PRIMARY KEY);
CREATE TABLE
复制

然后我们启动一个事务并插入数据:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7058
(1 row)

oid=# INSERT INTO show_xmin_xmax VALUES (1), (2);
INSERT 0 2
oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7058 |    0 |  1
 7058 |    0 |  2
(2 rows)

oid=# COMMIT;
COMMIT
复制

事务ID为7058,该值出现在xmin列中。xmax列为0,因为此数据未被删除(或更新)。

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7059
(1 row)

oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7058 |    0 |  1
 7058 |    0 |  2
(2 rows)

oid=# UPDATE show_xmin_xmax SET id = 3 WHERE id = 1;
UPDATE 1
oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7058 |    0 |  2
 7059 |    0 |  3
(2 rows)

oid=# COMMIT;
COMMIT
复制

数据更新后,会出现一个新的行版本,xmin=7059。我们在这里看不到的是,旧行(id=1)也仍然在表中,但是xmax值现在也设置为7059,并且对当前和任何更新的事务都不可见。现在我们看不到这个删除的行,但是它仍然存在。在这篇博文的后面,我们将学习如何使这些数据可见。

然而,有一个技巧可以在删除数据时使更改可见:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7060
(1 row)

oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7058 |    0 |  2
 7059 |    0 |  3
(2 rows)

oid=# DELETE FROM show_xmin_xmax WHERE id = 2 RETURNING xmin, xmax, *;
 xmin | xmax | id 
------+------+----
 7058 | 7060 |  2
(1 row)

DELETE 1
oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7059 |    0 |  3
(1 row)

oid=# COMMIT;
COMMIT
复制

DELETE的RETURNING子句还可以返回xmin和xmax列。正如我们在本例中所看到的,returnes数据的xmax设置为7060——当前事务ID。之后,对于常规查询,删除的数据不再可见。

cmin+cmax

与xmin和xmax一样,cmin和cmax存储事务信息。但是,这两列将命令序列号存储在事务中。假设您启动一个事务并执行多个命令:每次命令更改数据时,这些更改都会以增加的cmin和cmax信息存储。这有助于跟踪在事务期间的某个时间点发生的更改,并有助于以正确的顺序重播更改。示例表:

oid=# CREATE TABLE show_cmin_cmax (id INT PRIMARY KEY);
CREATE TABLE
复制

让我们插入一些数据,但这次在事务中使用多个命令:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7042
(1 row)

oid=# INSERT INTO show_cmin_cmax VALUES (1), (2);
INSERT 0 2
oid=# INSERT INTO show_cmin_cmax VALUES (3), (4);
INSERT 0 2
oid=# INSERT INTO show_cmin_cmax VALUES (5), (6);
INSERT 0 2
oid=# SELECT xmin, xmax, cmin, cmax, id FROM show_cmin_cmax order by id;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 |    0 |    0 |    0 |  1
 7042 |    0 |    0 |    0 |  2
 7042 |    0 |    1 |    1 |  3
 7042 |    0 |    1 |    1 |  4
 7042 |    0 |    2 |    2 |  5
 7042 |    0 |    2 |    2 |  6
(6 rows)

oid=# COMMIT;
COMMIT
复制

我们可以看到,所有行的xmin是7042,但是cmin和cmax随着每次插入而增加。使用相同INSERT命令插入的数据具有相同的cmin和cmax编号。让我们更新一些数据:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7043
(1 row)

oid=# SELECT xmin, xmax, cmin, cmax, id FROM show_cmin_cmax order by id;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 |    0 |    0 |    0 |  1
 7042 |    0 |    0 |    0 |  2
 7042 |    0 |    1 |    1 |  3
 7042 |    0 |    1 |    1 |  4
 7042 |    0 |    2 |    2 |  5
 7042 |    0 |    2 |    2 |  6
(6 rows)

oid=# UPDATE show_cmin_cmax SET id = 10 WHERE id = 1;
UPDATE 1
oid=# UPDATE show_cmin_cmax SET id = 20 WHERE id = 2;
UPDATE 1
oid=# SELECT xmin, xmax, cmin, cmax, id FROM show_cmin_cmax order by id;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 |    0 |    1 |    1 |  3
 7042 |    0 |    1 |    1 |  4
 7042 |    0 |    2 |    2 |  5
 7042 |    0 |    2 |    2 |  6
 7043 |    0 |    0 |    0 | 10
 7043 |    0 |    1 |    1 | 20
(6 rows)

oid=# COMMIT;
COMMIT
复制

我们可以看到xmin中的事务ID是如何变化的,因为更新发生在一个新的事务中(7043)。cmin和cmax再次从零开始。让我们试试删除并返回的技巧:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7044
(1 row)

oid=# SELECT xmin, xmax, cmin, cmax, id FROM show_cmin_cmax order by id;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 |    0 |    1 |    1 |  3
 7042 |    0 |    1 |    1 |  4
 7042 |    0 |    2 |    2 |  5
 7042 |    0 |    2 |    2 |  6
 7043 |    0 |    0 |    0 | 10
 7043 |    0 |    1 |    1 | 20
(6 rows)

oid=# DELETE FROM show_cmin_cmax WHERE id = 3 RETURNING xmin, xmax, cmin, cmax, *;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 | 7044 |    0 |    0 |  3
(1 row)

DELETE 1
oid=# DELETE FROM show_cmin_cmax WHERE id = 4 RETURNING xmin, xmax, cmin, cmax, *;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 | 7044 |    1 |    1 |  4
(1 row)

DELETE 1
oid=# SELECT xmin, xmax, cmin, cmax, id FROM show_cmin_cmax order by id;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 |    0 |    2 |    2 |  5
 7042 |    0 |    2 |    2 |  6
 7043 |    0 |    0 |    0 | 10
 7043 |    0 |    1 |    1 | 20
(4 rows)

oid=# COMMIT;
COMMIT
复制

因为两次删除都发生在同一个事务中(7044),所以返回的两行都将此事务ID显示为xmax。每个命令的cmin和cmax都在增加。

ctid

ctid列由两部分组成:表中的页面或块,以及在此页面中的位置。让我们来看一个稍微复杂一点的例子:

oid=# CREATE TABLE show_ctid (id INT PRIMARY KEY, data TEXT);
CREATE TABLE
oid=# ALTER TABLE show_ctid ALTER COLUMN data SET STORAGE plain;
ALTER TABLE
oid=# INSERT INTO show_ctid VALUES (1, 'Lorem'), (2, 'ipsum'), (3, 'dolor'), (4, 'sit'), (5, 'amet');
INSERT 0 5
复制

该表有一个整数作为主键,一个文本字段作为数据字段。Lorem ipsum的前5个单词用作文本。该列的存储类型更改为“普通”,以保持数据未压缩并在表本身中,并且不将其“外包”到TOAST表中。

oid=# \d+ show_ctid
                                 Table "public.show_ctid"
 Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
--------+---------+-----------+----------+---------+---------+--------------+-------------
 id     | integer |           | not null |         | plain   |              | 
 data   | text    |           |          |         | plain   |              | 
Indexes:
    "show_ctid_pkey" PRIMARY KEY, btree (id)
Access method: heap
复制

让我们看看ctid:

oid=# SELECT ctid, id, data FROM show_ctid order by id;
 ctid  | id | data  
-------+----+-------
 (0,1) |  1 | Lorem
 (0,2) |  2 | ipsum
 (0,3) |  3 | dolor
 (0,4) |  4 | sit
 (0,5) |  5 | amet
(5 rows)
复制

第0页,位置1到5。但这并没有真正的帮助,不是吗?幸运的是,有一个非常有用的扩展,允许我们深入了解细节:pageinspect。它由PostgreSQL contrib提供,应该可以在每个系统上使用。您需要安装它:

oid=# CREATE EXTENSION IF NOT EXISTS pageinspect;
CREATE EXTENSION
复制

现在,我们可以使用get_raw_page()函数来选择页面,并使用heap_page_items()对其进行解码:

oid=# SELECT *
oid-#   FROM heap_page_items(get_raw_page('show_ctid', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |         t_data         
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------------------
  1 |   8152 |        1 |     34 |   7049 |      0 |        0 | (0,1)  |           2 |       2306 |     24 |        |       | \x010000000d4c6f72656d
  2 |   8112 |        1 |     34 |   7049 |      0 |        0 | (0,2)  |           2 |       2306 |     24 |        |       | \x020000000d697073756d
  3 |   8072 |        1 |     34 |   7049 |      0 |        0 | (0,3)  |           2 |       2306 |     24 |        |       | \x030000000d646f6c6f72
  4 |   8040 |        1 |     32 |   7049 |      0 |        0 | (0,4)  |           2 |       2306 |     24 |        |       | \x0400000009736974
  5 |   8000 |        1 |     33 |   7049 |      0 |        0 | (0,5)  |           2 |       2306 |     24 |        |       | \x050000000b616d6574
(5 rows)
复制

有一个t_ctid,它是页面中的位置。这里描述了更多细节。还有t_数据,它保存了行数据。为了解码数据,我们需要知道这些数据是什么。首先,数据是bytea,因此是十六进制编码的。这可以解码:

oid=# SELECT *,
oid-#        encode(t_data, 'escape') AS decoded
oid-#   FROM heap_page_items(get_raw_page('show_ctid', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |         t_data         |           decoded           
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------------------+-----------------------------
  1 |   8152 |        1 |     34 |   7049 |      0 |        0 | (0,1)  |           2 |       2306 |     24 |        |       | \x010000000d4c6f72656d | \x01\000\000\000\rLorem
  2 |   8112 |        1 |     34 |   7049 |      0 |        0 | (0,2)  |           2 |       2306 |     24 |        |       | \x020000000d697073756d | \x02\000\000\000\ripsum
  3 |   8072 |        1 |     34 |   7049 |      0 |        0 | (0,3)  |           2 |       2306 |     24 |        |       | \x030000000d646f6c6f72 | \x03\000\000\000\rdolor
  4 |   8040 |        1 |     32 |   7049 |      0 |        0 | (0,4)  |           2 |       2306 |     24 |        |       | \x0400000009736974     | \x04\000\000\000        sit
  5 |   8000 |        1 |     33 |   7049 |      0 |        0 | (0,5)  |           2 |       2306 |     24 |        |       | \x050000000b616d6574   | \x05\000\000\000\x0Bamet
(5 rows)
复制

这有点道理。对于PostgreSQL,整个t_数据字段是来自每个列的数据的组合。由于表结构已知,因此不需要分隔符。让我们再次看看表结构:

1.id INT

2.数据文本

整数有4个字节宽,这意味着我们需要查看前4个字节:

  • 01\000\000\000 -> 01 00 00 00

  • 02\000\000\000 -> 02 00 00 00

  • 03\000\000\000 -> 03 00 00 00

  • 04\000\000\000 -> 04 00 00 00

这个系统是小端的,这意味着最低有效字节存储在最小(第一个)地址。这4个数字是id字段中的主键,扩展为32位(4字节),因为它是一个整数。

但是这一部分呢:“\rLorem”?这有点复杂。

它是一个文本字段(可变长度)。PostgreSQL中的文本是C结构:长度字段vl\u len,通常为4字节长。和数据字段vl_dat,可容纳高达1 GB的数据。指定长度允许PostgreSQL存储任意数据,包括C中著名的000。然而,它比这更复杂,vl_len保存更多信息。我们需要在位表示中查看此字段:

oid=# SELECT *,
oid-#        encode(t_data, 'escape') AS decoded,
oid-#        ('x' || encode(SUBSTR(t_data, 5, 1), 'hex'))::BIT(8) AS bits
oid-#   FROM heap_page_items(get_raw_page('show_ctid', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |         t_data         |           decoded           |   bits   
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------------------+-----------------------------+----------
  1 |   8152 |        1 |     34 |   7049 |      0 |        0 | (0,1)  |           2 |       2306 |     24 |        |       | \x010000000d4c6f72656d | \x01\000\000\000\rLorem     | 00001101
  2 |   8112 |        1 |     34 |   7049 |      0 |        0 | (0,2)  |           2 |       2306 |     24 |        |       | \x020000000d697073756d | \x02\000\000\000\ripsum     | 00001101
  3 |   8072 |        1 |     34 |   7049 |      0 |        0 | (0,3)  |           2 |       2306 |     24 |        |       | \x030000000d646f6c6f72 | \x03\000\000\000\rdolor     | 00001101
  4 |   8040 |        1 |     32 |   7049 |      0 |        0 | (0,4)  |           2 |       2306 |     24 |        |       | \x0400000009736974     | \x04\000\000\000        sit | 00001001
  5 |   8000 |        1 |     33 |   7049 |      0 |        0 | (0,5)  |           2 |       2306 |     24 |        |       | \x050000000b616d6574   | \x05\000\000\000\x0Bamet    | 00001011
(5 rows)
复制

当设置最低阶位(在小端上)时,长度字段仅为剩余的7位,而不是整个4个字节。这也意味着剩余数据的长度最多为127字节(2^7-1=127),总长度包括长度的字节。这是一种更有效地存储短值(最多127字节)的快捷方式。

如果未设置最低位,则第二个最低位用于存储更多信息。这将为长度信息保留30位(4字节=32位,减去2位状态),或为未压缩数据保留2^30-1=1073741823字节。Toast数据可以压缩,因此可以存储大约1 GB以上的数据。

在我们的例子中,为所有5行设置了最低位,我们需要查看其余位:

0000110 -> 6

0000110 -> 6

0000110 -> 6

0000100 -> 4

0000101 -> 5

6字节(“Lorem”=5字节,加长度字节)、4字节(“sit”=3字节,加上长度字节)和5字节(“Bamet”=4字节,加长字节)。

为了节省空间,较短的值将始终进入单字节形式。此信息的解码通常由C宏完成。

返回xmin和xmax

之前我们查看了show\u xmin\u xmax表,更新并删除了一些数据,但无法验证删除的数据是否仍在表中。pageinspect也有帮助:

oid=# SELECT *,
oid-#        encode(t_data, 'escape') AS decoded
oid-#   FROM heap_page_items(get_raw_page('show_xmin_xmax', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |   t_data   |     decoded      
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------+------------------
  1 |   8160 |        1 |     28 |   7058 |   7059 |        0 | (0,3)  |        8193 |       1280 |     24 |        |       | \x01000000 | \x01\000\000\000
  2 |   8128 |        1 |     28 |   7058 |   7060 |        0 | (0,2)  |        8193 |        256 |     24 |        |       | \x02000000 | \x02\000\000\000
  3 |   8096 |        1 |     28 |   7059 |      0 |        0 | (0,3)  |           1 |      10496 |     24 |        |       | \x03000000 | \x03\000\000\000
(3 rows)
复制

我们在表中看到3个条目:

  • 删除的条目(id=1)位于第一个位置,xmax为7059

  • 第二个条目是id=2,也被事务7060删除

  • 第三个条目是更新操作的结果,该操作将id=1行标记为已删除,同时插入id=3的新行,该操作的xmin=7059

让我们在表上运行VACUUM:

oid=# VACUUM show_xmin_xmax;
VACUUM
复制

再看看表:

oid=# SELECT *,
       encode(t_data, 'escape') AS decoded
  FROM heap_page_items(get_raw_page('show_xmin_xmax', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |   t_data   |     decoded      
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------+------------------
  1 |      0 |        0 |      0 |        |        |          |        |             |            |        |        |       |            | 
  2 |      0 |        0 |      0 |        |        |          |        |             |            |        |        |       |            | 
  3 |   8160 |        1 |     28 |   7059 |      0 |        0 | (0,3)  |           1 |      10496 |     24 |        |       | \x03000000 | \x03\000\000\000
(3 rows)
复制

前两个条目消失了,空间是空的,可以被未来的DML操作重新使用。然而,PostgreSQL不会删除空间,也不会将其返回给操作系统。让我们插入一个新行:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7061
(1 row)

oid=# INSERT INTO show_xmin_xmax VALUES (5);
INSERT 0 1
oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7059 |    0 |  3
 7061 |    0 |  5
(2 rows)

oid=# COMMIT;
COMMIT
复制

就一行, Vasili.

oid=# SELECT *,
       encode(t_data, 'escape') AS decoded
  FROM heap_page_items(get_raw_page('show_xmin_xmax', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |   t_data   |     decoded      
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------+------------------
  1 |   8128 |        1 |     28 |   7061 |      0 |        0 | (0,1)  |           1 |       2048 |     24 |        |       | \x05000000 | \x05\000\000\000
  2 |      0 |        0 |      0 |        |        |          |        |             |            |        |        |       |            | 
  3 |   8160 |        1 |     28 |   7059 |      0 |        0 | (0,3)  |           1 |      10496 |     24 |        |       | \x03000000 | \x03\000\000\000
(3 rows)
复制

数据库没有在页面末尾追加这一新行,也没有将其写入另一个页面。相反,它重新使用了可用空间,并将其写入ctid=(0,1)。

oid

最后一列是oid。如今,不再支持为行设置oid。在较新版本的PostgreSQL中,尝试添加OID字段会引发错误:

oid=# CREATE TABLE with_oid () WITH (OIDS=TRUE);
ERROR:  tables declared WITH OIDS are not supported
复制

在11之前的版本中,仍然支持但不鼓励这样做。这意味着,每个oid=TRUE的表都有一个名为“oid”的附加列,该列由不同的事务id填充,这增加了运行真空以防止事务id被包围的需要。

总结

每个PostgreSQL表都有几个额外的列,默认情况下这些列是隐藏的,但当包含在查询中时可以显示。了解每列的详细信息很有帮助,因为它可以很好地了解表结构和PostgreSQL组织数据的方式。

原文标题:ctid and other PostgreSQL table internals
原文作者:Andreas ‘ads’ Scherbaum
原文链接:https://andreas.scherbaum.la/blog/archives/1117-ctid-and-other-PostgreSQL-table-internals.html

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论