为什么*不*错误:索引“foo”的索引行大小 xxxx 超过最大值 2712?

Erw*_*ter 9 postgresql index size limits

我们一再看到尝试索引值超过最大大小的列失败。Postgres 10 有这样的错误信息:

ERROR:  index row size xxxx exceeds maximum 2712 for index "foo_idx"
HINT:  Values larger than 1/3 of a buffer page cannot be indexed.
       Consider a function index of an MD5 hash of the value, or use full text indexing.
Run Code Online (Sandbox Code Playgroud)

例子:

等等。

现在,a_horse_with_no_name 演示了一个具有更大text值(10000 个字符)的案例,它似乎仍然适用UNIQUE于 Postgres 9.6 中的索引。引用他的测试用例:

create table tbl (col text);
create unique index on tbl (col);

insert into tbl
values (rpad(md5(random()::text), 10000, md5(random()::text)));

select length(val) from x;  -- 10000
Run Code Online (Sandbox Code Playgroud)

没有错误,列值确实测试了 10000 个字符的长度。

最近有没有变化或者这怎么可能?

Erw*_*ter 14

简短的回答:压缩。

text默认情况下,数据类型允许(无损!)压缩和存储:

SELECT typstorage FROM pg_type WHERE typname = 'text';  -- 'x'
Run Code Online (Sandbox Code Playgroud)

手册关于pg_type.typstorage

p: Value must always be stored plain.
e: Value can be stored in a “secondary” relation (if relation has one, see pg_class.reltoastrelid).
m: Value can be stored compressed inline.
x: Value can be stored compressed inline or stored in “secondary” storage.
Run Code Online (Sandbox Code Playgroud)

请注意,也可以将 m 列移出到辅助存储,但只能作为最后的手段(首先移出 e 和 x 列)。

使用pg_column_size()而不是 进行测试length()。一定要测试实际的表列(应用压缩)而不仅仅是输入值。看:

CREATE TABLE tbl (id int, col text);
INSERT INTO tbl(id, col) VALUES 
   (1, rpad(md5('non_random'::text),     100, md5('non_random'::text)))
 , (2, rpad(md5('non_random'::text),    1000, md5('non_random'::text)))
 , (3, rpad(md5('non_random'::text),   10000, md5('non_random'::text)))
 , (4, rpad(md5('non_random'::text),  100000, md5('non_random'::text)))
 , (5, rpad(md5('non_random'::text),  500000, md5('non_random'::text)))
 , (6, rpad(md5('non_random'::text), 1000000, md5('non_random'::text))); 

SELECT id, left(col, 10) || ' ...' AS col
     , length(col) AS char_length
     , pg_column_size(col) AS compressed
     , pg_column_size(col || '') AS uncompressed
FROM   tbl ORDER BY id; 
Run Code Online (Sandbox Code Playgroud)
SELECT typstorage FROM pg_type WHERE typname = 'text';  -- 'x'
Run Code Online (Sandbox Code Playgroud)
SELECT pg_column_size(rpad(md5('non_random'::text), 1000000, md5('non_random'::text)));
Run Code Online (Sandbox Code Playgroud)
p: Value must always be stored plain.
e: Value can be stored in a “secondary” relation (if relation has one, see pg_class.reltoastrelid).
m: Value can be stored compressed inline.
x: Value can be stored compressed inline or stored in “secondary” storage.
Run Code Online (Sandbox Code Playgroud)

db<>在这里摆弄

请注意如何使用 noop 表达式强制将值从其存储格式中解压缩:pg_column_size(col || '')

第 5 行太大而无法容纳索引元组(即使使用压缩)并触发标题中的错误消息。

第 6 行会很大以适应索引并触发相关错误消息:

错误:索引行需要 11504 字节,最大大小为 8191

生成的测试值rpad()具有重复模式,允许大规模压缩。即使很长的琴弦也很容易适应最大值。这样压缩后的大小。

有关的:

长答案

我进行了更广泛的测试,篡改了存储内部结构以验证我的理解。仅供测试使用!

dbfiddle 不允许对系统目录进行写访问。但查询是在那里尝试“在家”。