Postgresql 重新运行时使用未执行计划的Postgres
我正在导入一个非圆形图,并将祖先按代码展平为一个数组。这可以正常工作(有一点):~900k边上的400k代码使用~45s 但是,在第一次成功执行后,Postgres决定停止使用Postgresql 重新运行时使用未执行计划的Postgres,postgresql,query-optimization,Postgresql,Query Optimization,我正在导入一个非圆形图,并将祖先按代码展平为一个数组。这可以正常工作(有一点):~900k边上的400k代码使用~45s 但是,在第一次成功执行后,Postgres决定停止使用嵌套循环,更新查询性能急剧下降:每个代码大约2个 我可以通过在更新之前设置一个真空来强制解决这个问题,但我很好奇为什么会出现这种未优化的情况 DROP TABLE IF EXISTS tmp_anc; DROP TABLE IF EXISTS tmp_rel; DROP TABLE IF EXISTS tmp_edges;
嵌套循环
,更新查询性能急剧下降:每个代码大约2个
我可以通过在更新之前设置一个真空
来强制解决这个问题,但我很好奇为什么会出现这种未优化的情况
DROP TABLE IF EXISTS tmp_anc;
DROP TABLE IF EXISTS tmp_rel;
DROP TABLE IF EXISTS tmp_edges;
DROP TABLE IF EXISTS tmp_codes;
CREATE TABLE tmp_rel (
from_id BIGINT,
to_id BIGINT,
);
COPY tmp_rel FROM 'rel.txt' WITH DELIMITER E'\t' CSV HEADER;
CREATE TABLE tmp_edges(
start_node BIGINT,
end_node BIGINT
);
INSERT INTO tmp_edges(start_node, end_node)
SELECT from_id AS start_node, to_id AS end_node
FROM tmp_rel;
CREATE INDEX tmp_edges_end ON tmp_edges (end_node);
CREATE TABLE tmp_codes (
id BIGINT,
active SMALLINT,
);
COPY tmp_codes FROM 'codes.txt' WITH DELIMITER E'\t' CSV HEADER;
CREATE TABLE tmp_anc(
code BIGINT,
ancestors BIGINT[]
);
INSERT INTO tmp_anc
SELECT DISTINCT(id)
FROM tmp_codes
WHERE active = 1;
CREATE INDEX tmp_anc_codes ON tmp_anc_codes (code);
VACUUM; -- Need this for the update to execute optimally
UPDATE tmp_anc sa SET ancestors = (
WITH RECURSIVE ancestors(code) AS (
SELECT start_node FROM tmp_edges WHERE end_node = sa.code
UNION
SELECT se.start_node
FROM tmp_edges se, ancestors a
WHERE se.end_node = a.code
)
SELECT array_agg(code) FROM ancestors
);
表统计:
tmp_rel 507 MB 0 bytes
tmp_edges 74 MB 37 MB
tmp_codes 32 MB 0 bytes
tmp_anc 22 MB 8544 kB
说明:
更新前没有真空:
Update on tmp_anc sa (cost=10000000000.00..11081583053.74 rows=10 width=46) (actual time=38294.005..38294.005 rows=0 loops=1)
-> Seq Scan on tmp_anc sa (cost=10000000000.00..11081583053.74 rows=10 width=46) (actual time=3300.974..38292.613 rows=10 loops=1)
SubPlan 2
-> Aggregate (cost=108158305.25..108158305.26 rows=1 width=32) (actual time=3829.253..3829.253 rows=1 loops=10)
CTE ancestors
-> Recursive Union (cost=81.97..66015893.05 rows=1872996098 width=8) (actual time=0.037..3827.917 rows=45 loops=10)
-> Bitmap Heap Scan on tmp_edges (cost=81.97..4913.18 rows=4328 width=8) (actual time=0.022..0.022 rows=2 loops=10)
Recheck Cond: (end_node = sa.code)
Heap Blocks: exact=12
-> Bitmap Index Scan on tmp_edges_end (cost=0.00..80.89 rows=4328 width=0) (actual time=0.014..0.014 rows=2 loops=10)
Index Cond: (end_node = sa.code)
-> Merge Join (cost=4198.89..2855105.79 rows=187299177 width=8) (actual time=163.746..425.295 rows=10 loops=90)
Merge Cond: (a.code = se.end_node)
-> Sort (cost=4198.47..4306.67 rows=43280 width=8) (actual time=0.012..0.016 rows=5 loops=90)
Sort Key: a.code
Sort Method: quicksort Memory: 25kB
-> WorkTable Scan on ancestors a (cost=0.00..865.60 rows=43280 width=8) (actual time=0.000..0.001 rows=5 loops=90)
-> Materialize (cost=0.42..43367.08 rows=865523 width=16) (actual time=0.010..337.592 rows=537171 loops=90)
-> Index Scan using tmp_edges_end on edges se (cost=0.42..41203.27 rows=865523 width=16) (actual time=0.009..247.547 rows=537171 loops=90)
-> CTE Scan on ancestors (cost=0.00..37459921.96 rows=1872996098 width=8) (actual time=1.227..3829.159 rows=45 loops=10)
Update on tmp_anc sa (cost=0.00..2949980136.43 rows=387059 width=14) (actual time=74701.329..74701.329 rows=0 loops=1)
-> Seq Scan on tmp_anc sa (cost=0.00..2949980136.43 rows=387059 width=14) (actual time=0.336..70324.848 rows=387059 loops=1)
SubPlan 2
-> Aggregate (cost=7621.50..7621.51 rows=1 width=8) (actual time=0.180..0.180 rows=1 loops=387059)
CTE ancestors
-> Recursive Union (cost=0.42..7583.83 rows=1674 width=8) (actual time=0.005..0.162 rows=32 loops=387059)
-> Index Scan using tmp_edges_end on tmp_edges (cost=0.42..18.93 rows=4 width=8) (actual time=0.004..0.005 rows=2 loops=387059)
Index Cond: (end_node = sa.code)
-> Nested Loop (cost=0.42..753.14 rows=167 width=8) (actual time=0.003..0.019 rows=10 loops=2700448)
-> WorkTable Scan on ancestors a (cost=0.00..0.80 rows=40 width=8) (actual time=0.000..0.001 rows=5 loops=2700448)
-> Index Scan using tmp_edges_end on tmp_edges se (cost=0.42..18.77 rows=4 width=16) (actual time=0.003..0.003 rows=2 loops=12559395)
Index Cond: (end_node = a.code)
-> CTE Scan on ancestors (cost=0.00..33.48 rows=1674 width=8) (actual time=0.007..0.173 rows=32 loops=387059)
更新前使用真空吸尘器:
Update on tmp_anc sa (cost=10000000000.00..11081583053.74 rows=10 width=46) (actual time=38294.005..38294.005 rows=0 loops=1)
-> Seq Scan on tmp_anc sa (cost=10000000000.00..11081583053.74 rows=10 width=46) (actual time=3300.974..38292.613 rows=10 loops=1)
SubPlan 2
-> Aggregate (cost=108158305.25..108158305.26 rows=1 width=32) (actual time=3829.253..3829.253 rows=1 loops=10)
CTE ancestors
-> Recursive Union (cost=81.97..66015893.05 rows=1872996098 width=8) (actual time=0.037..3827.917 rows=45 loops=10)
-> Bitmap Heap Scan on tmp_edges (cost=81.97..4913.18 rows=4328 width=8) (actual time=0.022..0.022 rows=2 loops=10)
Recheck Cond: (end_node = sa.code)
Heap Blocks: exact=12
-> Bitmap Index Scan on tmp_edges_end (cost=0.00..80.89 rows=4328 width=0) (actual time=0.014..0.014 rows=2 loops=10)
Index Cond: (end_node = sa.code)
-> Merge Join (cost=4198.89..2855105.79 rows=187299177 width=8) (actual time=163.746..425.295 rows=10 loops=90)
Merge Cond: (a.code = se.end_node)
-> Sort (cost=4198.47..4306.67 rows=43280 width=8) (actual time=0.012..0.016 rows=5 loops=90)
Sort Key: a.code
Sort Method: quicksort Memory: 25kB
-> WorkTable Scan on ancestors a (cost=0.00..865.60 rows=43280 width=8) (actual time=0.000..0.001 rows=5 loops=90)
-> Materialize (cost=0.42..43367.08 rows=865523 width=16) (actual time=0.010..337.592 rows=537171 loops=90)
-> Index Scan using tmp_edges_end on edges se (cost=0.42..41203.27 rows=865523 width=16) (actual time=0.009..247.547 rows=537171 loops=90)
-> CTE Scan on ancestors (cost=0.00..37459921.96 rows=1872996098 width=8) (actual time=1.227..3829.159 rows=45 loops=10)
Update on tmp_anc sa (cost=0.00..2949980136.43 rows=387059 width=14) (actual time=74701.329..74701.329 rows=0 loops=1)
-> Seq Scan on tmp_anc sa (cost=0.00..2949980136.43 rows=387059 width=14) (actual time=0.336..70324.848 rows=387059 loops=1)
SubPlan 2
-> Aggregate (cost=7621.50..7621.51 rows=1 width=8) (actual time=0.180..0.180 rows=1 loops=387059)
CTE ancestors
-> Recursive Union (cost=0.42..7583.83 rows=1674 width=8) (actual time=0.005..0.162 rows=32 loops=387059)
-> Index Scan using tmp_edges_end on tmp_edges (cost=0.42..18.93 rows=4 width=8) (actual time=0.004..0.005 rows=2 loops=387059)
Index Cond: (end_node = sa.code)
-> Nested Loop (cost=0.42..753.14 rows=167 width=8) (actual time=0.003..0.019 rows=10 loops=2700448)
-> WorkTable Scan on ancestors a (cost=0.00..0.80 rows=40 width=8) (actual time=0.000..0.001 rows=5 loops=2700448)
-> Index Scan using tmp_edges_end on tmp_edges se (cost=0.42..18.77 rows=4 width=16) (actual time=0.003..0.003 rows=2 loops=12559395)
Index Cond: (end_node = a.code)
-> CTE Scan on ancestors (cost=0.00..33.48 rows=1674 width=8) (actual time=0.007..0.173 rows=32 loops=387059)
第一个执行计划的估计值非常差(
tmp\u edges\u end上的位图索引扫描
估计值为4328,而不是2行),而第二个执行计划的估计值很好,因此选择了一个好的计划。
所以你上面引用的两次执行之间的某些东西一定改变了估计
此外,您说第一次执行UPDATE
(我们没有EXPLAIN(ANALYZE)
输出)很快
对于初始性能下降的唯一好解释是,autovacuum守护进程需要一些时间来收集新表的统计信息。这通常会提高查询性能,但当然也可以反过来工作
另外,VACUUM
通常不能解决性能问题。是否您使用了真空(分析)
在初次更新之前收集统计数据时,了解情况会很有趣:
ANALYZE tmp_edges;
然而,当我更仔细地阅读您的查询时,我想知道为什么要使用相关子查询来实现这一点。也许这样做会更快:
UPDATE tmp_anc sa
SET ancestors = a.codes
FROM (WITH RECURSIVE ancestors(code, start_node) AS
(SELECT tmp_anc.code, tmp_edges.start_node
FROM tmp_edges
JOIN tmp_anc ON tmp_edges.end_node = tmp_anc.code
UNION
SELECT a.code, se.start_node
FROM tmp_edges se
JOIN ancestors a ON se.end_node = a.code
)
SELECT code,
array_agg(start_node) AS codes
FROM ancestors
GROUP BY (code)
) a
WHERE sa.code = a.code;
(这未经测试,因此可能存在错误。)第一个执行计划的估计值非常差(tmp_边上的位图索引扫描
估计值为4328,而不是2行),而第二个执行计划的估计值很好,因此选择了一个好的计划。
所以你上面引用的两次执行之间的某些东西一定改变了估计
此外,您说第一次执行UPDATE
(我们没有EXPLAIN(ANALYZE)
输出)很快
对于初始性能下降的唯一好解释是,autovacuum守护进程需要一些时间来收集新表的统计信息。这通常会提高查询性能,但当然也可以反过来工作
另外,VACUUM
通常不能解决性能问题。是否您使用了真空(分析)
在初次更新之前收集统计数据时,了解情况会很有趣:
ANALYZE tmp_edges;
然而,当我更仔细地阅读您的查询时,我想知道为什么要使用相关子查询来实现这一点。也许这样做会更快:
UPDATE tmp_anc sa
SET ancestors = a.codes
FROM (WITH RECURSIVE ancestors(code, start_node) AS
(SELECT tmp_anc.code, tmp_edges.start_node
FROM tmp_edges
JOIN tmp_anc ON tmp_edges.end_node = tmp_anc.code
UNION
SELECT a.code, se.start_node
FROM tmp_edges se
JOIN ancestors a ON se.end_node = a.code
)
SELECT code,
array_agg(start_node) AS codes
FROM ancestors
GROUP BY (code)
) a
WHERE sa.code = a.code;
(这是未经测试的,因此可能会有错误。)如果没有第一次执行的解释(分析)
输出,很难说,但是可能真空
刚好为自动分析留出了足够的时间来收集统计数据?如果在更新
之前对表进行分析
,是否仍能观察到效果?添加了解释分析,但必须将代码量从400k更改为10,以便运行到完成<代码>合并联接(cost=4198.89..2855105.79行=187299177
:(所有表似乎都没有主键(或唯一索引),您还应该在填充表后对表运行vacuum analyze
,这将更新统计信息。用于更新的仅有两个表是tmp\u anc
和tmp\u edges
。我认为tmp\u anc
不需要PK,因为它的code
列是迭代的主题,而tmp>是_anc
在end_node
上有一个索引,该索引被大量使用。不过,为了排除这一点,添加了PKs,性能没有变化。实际上,只是想弄清楚如何在第一次执行时完美地执行查询,但后续执行需要一个真空。没有解释(分析)很难说
第一次执行时的输出,但可能是真空
刚好为自动分析留出了足够的时间来收集统计数据?如果在更新之前分析表
,您仍然可以观察效果吗?添加了解释分析,但必须将代码量从400k更改为10,以便运行到完成。>合并联接(cost=4198.89..2855105.79行=187299177
:(所有表似乎都没有主键(或唯一索引),您还应该在填充表后对表运行vacuum analyze
,这将更新统计信息。用于更新的仅有两个表是tmp\u anc
和tmp\u edges
。我认为tmp\u anc
不需要PK,因为它的code
列是迭代的主题,而tmp>是_anc
在end_node
上有一个索引,该索引被大量使用。不过,为了排除这种可能性,添加了PKs,性能没有变化。实际上,只是想弄清楚如何在第一次执行时完美地执行查询,但后续执行需要一个真空。第二个解释分析发布在上面(正确规划的查询)是第一次执行。之后的每一次尝试都是缓慢的。感谢您发布您对查询的优化,我将在今天晚些时候进行研究。只是重申一下,我第一次运行此查询时,查询采用了优化的路线,之后的每一次都会在不强制进行新统计的情况下,显示其性能显著下降。请解释“新统计信息”:你说你正在运行VACUUM
,它不收集统计信息。关于你的第一个执行计划,另一个奇怪的事情是它看起来像enable\u seqscan
被设置为off
(观察顺序扫描的奇怪的高启动成本).您是否在以下两种情况下更改优化器参数