Sha*_*ane 5 postgresql index count distinct
考虑以下示例:
CREATE TABLE test (
id SERIAL,
some_integer INT
);
INSERT INTO test (some_integer)
SELECT FLOOR(RANDOM()*100000) from generate_series(1,100000) s(i);
CREATE INDEX some_integer_idx ON test (some_integer);
EXPLAIN ANALYZE SELECT COUNT(DISTINCT some_integer) from test;
Run Code Online (Sandbox Code Playgroud)
它返回以下查询计划:
CREATE TABLE test (
id SERIAL,
some_integer INT
);
INSERT INTO test (some_integer)
SELECT FLOOR(RANDOM()*100000) from generate_series(1,100000) s(i);
CREATE INDEX some_integer_idx ON test (some_integer);
EXPLAIN ANALYZE SELECT COUNT(DISTINCT some_integer) from test;
Run Code Online (Sandbox Code Playgroud)
我很惊讶它仍然在测试中进行顺序扫描。简单地计算索引中的行数不是更快吗?
B 树索引中的每一行都有一个单独的条目,对于重复项也是如此。所以我们永远不能“简单地统计索引中的行数”。需要读取和聚合的索引行数与表行数一样多。
\nPostgres 13 或更高版本中的索引重复数据删除可以压缩重复项,提供更多优化潜力,但主要逻辑不变(1 行,1 个索引元组)。看:
\n\n由于 B 树索引通常小于其表,因此仅索引扫描仍然是一个有吸引力的选择。但这会增加一些开销:检查可见性映射、堆获取非“全部可见”的数据页。而且索引往往比表更膨胀(失去了最初的优势)。索引默认为fillfactor
90,而表默认为fillfactor
100 。并且可见性映射必须是最新的 - 表必须被充分清理。添加VACUUM ANALYZE
到您的测试中作为开始。
您的测试表有 8 个字节的数据,这是可能的最小行大小。而且您创建的重复项非常少。因此,即使在完美的条件下,仅索引扫描也几乎没有什么好处。Postgres 甚至没有尝试。
\n添加有效负载列以增加表行,并创建更多重复项,瞧\xc3\xa1,我们看到仅索引扫描:
\nCREATE TABLE test (\n id serial\n, some_integer int\n, payload text\n);\n\nINSERT INTO test (some_integer, payload)\nSELECT trunc(random() * 10000) -- fewer distinct values, some dupes\n , \'Lorem ipsum dolor sit amet, consectetur adipisicing elit\' -- bigger row !!\nFROM generate_series(1,100000) s;\n\nCREATE INDEX some_integer_idx ON test (some_integer);\n
Run Code Online (Sandbox Code Playgroud)\nVACUUM ANALYZE test;\n
Run Code Online (Sandbox Code Playgroud)\nEXPLAIN ANALYZE SELECT count(DISTINCT some_integer) FROM test;\n
Run Code Online (Sandbox Code Playgroud)\n聚合(成本=2210.29..2210.30行=1宽度=8)(实际时间=25.763..25.764行=1循环=1)\n-> 仅在测试中使用some_integer_idx进行索引扫描(成本=0.29..1960.29行= 100000宽度=4)(实际时间=0.030..12.562行=100000循环=1)\n堆获取:0\n计划时间:0.184毫秒\n执行时间:25.891毫秒\n
db<>在这里摆弄
\n优势随着相对大小障碍表 >> 索引、重复项的百分比以及“所有可见”数据页的部分(更新的可见性图)而增长。
\n现在,如果已经实现了索引跳过扫描,则即使没有有效负载列,索引也可以很好地用于具有许多重复项的情况(与您的示例不同)。但我们还没有做到这一点(Postgres 15)。
\n在那之前我们可以模拟索引跳过扫描:
\nWITH RECURSIVE cte AS (\n (\n SELECT some_integer AS i\n FROM test\n ORDER BY 1\n LIMIT 1\n )\n \n UNION ALL\n SELECT (SELECT t.some_integer\n FROM test t\n WHERE t.some_integer > c.i\n ORDER BY 1\n LIMIT 1)\n FROM cte c\n WHERE c.i IS NOT NULL\n )\nSELECT count(i) FROM cte;\n
Run Code Online (Sandbox Code Playgroud)\ndb<>在这里摆弄
\n快多了。看:
\n