【发布时间】:2021-03-31 14:34:19
【问题描述】:
目前我面临一个问题,即 Postgres 的查询规划器根据(我认为的)错误估计做出错误的决定,即在更大的查询中,查询规划器选择从这篇文章中进行(散列)连接为这是第一个/最里面的部分,因为这个连接的估计只有 274 行,但实际上连接这两个表时有 31770 行。这会导致在更大的查询中对这 31770 行进行嵌套循环,尽管当查询计划器考虑/知道正确的行数时肯定会有更便宜的路径,即估计会更好。
这可以用来重现问题:
CREATE TABLE b (id int primary key, name text, is_visible boolean);
INSERT INTO b (id, name, is_visible) SELECT x, 'B #' || x, CASE WHEN x % 116 = 0 THEN true ELSE false END FROM generate_series(1, 29499) AS x;
CREATE INDEX ON b (is_visible);
CREATE TABLE a (id bigserial primary key, b_id int references b(id), name text);
WITH dist AS (
SELECT '{7236,4431,3012,2339,2246,1907,1661,1356,1173,1029,533,505,415,354,336,275,188,168,168,153,152,133,126,125,113,90,73,72,65,64,64,48,46,35,34,31,26,26,26,25,25,25,24,22,22,21,20,20,19,19,15,15,15,15,13,13,12,12,12,12,12,11,11,11,11,8,8,8,8,8,8,8,8,7,7,7,6,6,6,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::INT[] cnt
)
INSERT INTO a (b_id, name)
SELECT x.id, x.index || '/' || y
FROM (SELECT id, row_number() over (ORDER BY id) AS index FROM b WHERE is_visible = true) AS x,
generate_series(1, (SELECT cnt[x.index] FROM dist)) AS y
ORDER BY x.id;
我试图实现相同的数据分布,因此出现了带有计数的奇怪数组。这实际上似乎奏效了,因为当我对测试数据执行以下查询时,估计值和实际行完全相同:
postgres=> explain analyze select * from a join b on b.id = a.b_id where b.is_visible=true;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=35.88..841.99 rows=274 width=31) (actual time=5.251..637.037 rows=31770 loops=1)
Hash Cond: (a.b_id = b.id)
-> Seq Scan on a (cost=0.00..722.70 rows=31770 width=18) (actual time=0.020..205.874 rows=31770 loops=1)
-> Hash (cost=32.70..32.70 rows=254 width=13) (actual time=5.195..5.212 rows=254 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 20kB
-> Index Scan using b_is_visible_idx on b (cost=0.29..32.70 rows=254 width=13) (actual time=0.044..2.728 rows=254 loops=1)
Index Cond: (is_visible = true)
Planning Time: 0.374 ms
Execution Time: 836.821 ms
(9 rows)
我可以做些什么来优化估算?
[编辑]
我可能问错了问题,但我实际上对优化这个特定查询并不感兴趣,而是了解 274 的估计值来自何处以及如何更接近实际行数 31770 的估计值
[编辑 2]
使用 Postgres 12.4
【问题讨论】:
-
如果我没记错的话,过去统计/MCV 存在问题,该问题已得到纠正。请将您的 Postgres 版本添加到问题中。
-
众所周知,这种错误估计很难纠正。您可能必须为该查询禁用嵌套循环。
-
感谢您的提示,我已将版本添加到原始帖子中。关于禁用嵌套循环不是一个选项,因为较大的查询会执行 4 个额外的连接,并且完全禁用嵌套循环会很痛。但真正有帮助的是
SET join_collapse_limit = 1;用于更大的查询,据我所知,这会禁用 Postgres 的优化并按照我编写的方式执行查询。但我不知道,我宁愿纠正错误估计,也可能从 Postgres 获得优化,这可能实际上有助于提高性能,而不是禁用所有优化。
标签: postgresql query-optimization