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


16

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

create table t(id serial);
insert into t default values;
insert into t default values;
select ctid
     , id
from t;
ctid | ID
:---- | -:
(0,1)| 1个
(0,2)| 2

dbfiddle 在这里

是什么让刚刚页面数从最好的方式ctid在最适合的类型(例如integerbigintnumeric(1000,0))?

我能想到唯一方法是非常丑陋。


1
IIRC是向量类型,我们在这些类型上没有访问器方法。我不确定是否可以通过C函数来实现。克雷格(Craig)会确定的:)
2014年

2
您可以投射为POINT吗?例如。select ct[0], ct[1] from (select ctid::text::point as ct from pg_class where ...) y;
2014年

1
标题建议您同时在页码元组索引之后,然后再缩小到页码。我去了体内的版本,元组索引是一个琐碎的扩展。
Erwin Brandstetter,2014年

Answers:


21
SELECT (ctid::text::point)[0]::bigint AS page_number FROM t;

您在摆弄我的解决方案。

@bma已经在评论中暗示了类似的内容。这里有一个 ...

类型的基本原理

ctid是类型tid(元组标识符),ItemPointer在C代码中调用。每个文档:

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

大胆强调我的。和:

ItemPointer,也称为CTID

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

SELECT (2^45 / 2^13)::int      -- = 2^32 = 4294967294

这将适合一个未签名的integer。经过进一步调查,我在源代码中发现...

块按从0到0xFFFFFFFE的顺序编号。

大胆强调我的。确认第一个计算:

SELECT 'xFFFFFFFE'::bit(32)::int8 -- max page number: 4294967294

Postgres使用带符号的整数,因此短了一位。但是,我无法确定文本表示是否已移动以容纳带符号的整数。在有人可以解决之前,我会退回到bigint,无论如何都可以。

没有注册投tid在Postgres的9.3类型:

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

 castsource | casttarget | castfunc | castcontext | castmethod
------------+------------+----------+-------------+------------
(0 rows)

您仍然可以投射到text。有一个在Postgres的一切文本表示

另一个重要的例外是,“自动I / O转换强制转换”未使用明确表示,“自动I / O转换强制转换”是使用数据类型自己的I / O函数在文本或其他字符串类型之间进行转换 pg_cast

文本表示与包含两个float8数字的点的表示形式匹配,该表示形式是无损的。

您可以使用索引0访问点的第一个数字bigint。Voilá。

性能

我在一个包含3万行(最好是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;

int而不是bigint这里,对于测试的目的来说大多无关紧要。我没有重复bigint。强制转换
t_tid基于用户定义的复合类型,例如@Jake注释。
要点:铸造往往比字符串处理要快。正则表达式很昂贵。以上解决方案是最短和最快的。


1
感谢Erwin,有用的东西。从这里看起来像是ctid6个字节,其中4个用于页面,2个用于行。我曾担心要强制转换,float但我想我不需要您在这里说的话。看来用户定义的复合类型要比使用慢得多point,您也发现吗?
杰克说尝试topanswers.xyz 2014年

@JackDouglas:经过进一步调查,我回到了bigint。考虑更新。
Erwin Brandstetter,2014年

1
@JackDouglas:我喜欢您关于转换为复合类型的想法。它很干净,并且效果非常好-即使point向后和向后的投射int8仍然更快)。强制转换为预定义的类型总是会更快一些。我将其添加到测试中以进行比较。我会确保这一点(page_number bigint, row_number integer)
Erwin Brandstetter,2014年

1
2^40仅是1TB,不32TB其是2^45,它除以2^13给出2^32,因此完整的32个比特是必需的页码。
DanielVérité2014年

1
也或许值得一提的是,pg_freespacemap用途bigint为BLKNO
杰克说试topanswers.xyz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.