如何将ctid分解为页码和行号?

Jac*_*las 19 postgresql data-pages datatypes cast postgresql-9.4

表中的每一行都有一个系统列 ctid,其类型tid表示该行的物理位置:

create table t(id serial);
insert into t default values;
insert into t default values;
Run Code Online (Sandbox Code Playgroud)
select ctid
     , id
from t;
Run Code Online (Sandbox Code Playgroud)
ctid | ID
:---- | -:
(0,1) | 1
(0,2) | 2

dbfiddle在这里

ctid最合适的类型(例如integerbigintnumeric(1000,0))中获取页码的最佳方法是什么?

我能想到的唯一的办法是非常难看。

Erw*_*ter 26

SELECT (ctid::text::point)[0]::bigint AS block_number FROM t;
Run Code Online (Sandbox Code Playgroud)

db<>在这里摆弄

@bma 在他的评论中提出了类似的建议。这里有一个 ...

类型的基本原理

ctid是类型tid(元组标识符),ItemPointer在 C 语言源代码中调用。手册:

这是系统列的数据类型ctid。元组 ID 是一对(块编号内的元组索引),用于标识行在其表中的物理位置。

大胆强调我的。和:

( ItemPointer, 也称为CTID)

在标准安装中,一个块为8 KB最大表大小为32 TB从逻辑上讲,块编号必须至少容纳最大值(根据@Daniel 的评论固定):

SELECT (2^45 / 2^13)::int      -- = 2^32 = 4294967294
Run Code Online (Sandbox Code Playgroud)

这将适合 unsigned integer。在进一步调查中,我在源代码中发现......

块按顺序编号,从0 到 0xFFFFFFFE

大胆强调我的。这证实了第一个计算:

SELECT 'xFFFFFFFE'::bit(32)::int8 -- max page number: 4294967294
Run Code Online (Sandbox Code Playgroud)

Postgres 使用有符号整数,因此有点短。然而,我无法确定文本表示是否被移动以适应有符号整数。在有人解决这个问题之前,我会退回到bigint,这在任何情况下都有效。

投掷

没有注册投tid在Postgres的9.3型(从Postgres 13依然如此):

SELECT *
FROM   pg_cast
WHERE  castsource = 'tid'::regtype
OR     casttarget = 'tid'::regtype;

 castsource | casttarget | castfunc | castcontext | castmethod
------------+------------+----------+-------------+------------
(0 rows)
Run Code Online (Sandbox Code Playgroud)

您仍然可以投射到text. Postgres 中的每种类型都有一个文本表示

另一个重要的例外是“自动 I/O 转换转换”,即使用数据类型自己的 I/O 函数执行的转换为文本或其他字符串类型的转换,在 pg_cast.

文本表示匹配由两个float8数字组成的点的表示,该点是无损的。

您可以访问索引为 0 的点的第一个数字。转换为bigint。瞧。

表现

我在 Postgres 9.4 中对一个包含 30k 行(最好是 5 个)的表进行了快速测试,我想到了几个表达式,包括您的原始表达式:

SELECT (ctid::text::point)[0]::int                              --  25 ms
     , right(split_part(ctid::text, ',', 1), -1)::int           --  28 ms
     , ltrim(split_part(ctid::text, ',', 1), '(')::int          --  29 ms
     , (ctid::text::t_tid).page_number                          --  31 ms
     , (translate(ctid::text,'()', '{}')::int[])[1]             --  45 ms
     , (replace(replace(ctid::text,'(','{'),')','}')::int[])[1] --  51 ms
     , substring(right(ctid::text, -1), '^\d+')::int            --  52 ms
     , substring(ctid::text, '^\((\d+),')::int                  -- 143 ms
FROM   tbl;
Run Code Online (Sandbox Code Playgroud)

int而不是bigint,与测试目的大多无关。我最终在bigint具有 50k 行的表上重复了 Postgres 13 中的测试。结果大体相同!

t_tid强制转换建立在用户定义的复合类型上,就像@Jake 评论的那样。
它的要点:转换往往比字符串操作更快。正则表达式很昂贵。上面的解决方案是最短和最快的。