PostgreSQL 可以使用索引来加速计数(不同)查询吗?

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)

我很惊讶它仍然在测试中进行顺序扫描。简单地计算索引中的行数不是更快吗?

Erw*_*ter 9

B 树索引中的每一行都有一个单独的条目,对于重复项也是如此。所以我们永远不能“简单地统计索引中的行数”。需要读取和聚合的索引行数与表行数一样多。

\n

Postgres 13 或更高版本中的索引重复数据删除可以压缩重复项,提供更多优化潜力,但主要逻辑不变(1 行,1 个索引元组)。看:

\n\n

由于 B 树索引通常小于其表,因此仅索引扫描仍然是一个有吸引力的选择。但这会增加一些开销:检查可见性映射、堆获取非“全部可见”的数据页。而且索引往往比表更膨胀(失去了最初的优势)。索引默认为fillfactor90,而表默认为fillfactor100 。并且可见性映射必须是最新的 - 表必须被充分清理。添加VACUUM ANALYZE到您的测试中作为开始。

\n

您的测试表有 8 个字节的数据,这是可能的最小行大小。而且您创建的重复项非常少。因此,即使在完美的条件下,仅索引扫描也几乎没有什么好处。Postgres 甚至没有尝试。

\n

添加有效负载列以增加表行,并创建更多重复项,瞧\xc3\xa1,我们看到仅索引扫描

\n
CREATE 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)\n
VACUUM ANALYZE test;\n
Run Code Online (Sandbox Code Playgroud)\n
EXPLAIN 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

优化许多重复项

\n

现在,如果已经实现了索引跳过扫描,则即使没有有效负载列,索引也可以很好地用于具有许多重复项的情况(与您的示例不同)。但我们还没有做到这一点(Postgres 15)

\n

在那之前我们可以模拟索引跳过扫描:

\n
WITH 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)\n

db<>在这里摆弄

\n

快多了。看:

\n\n