使用嵌套循环提高 SQL 查询的性能 - PostgreSQL
Posted
技术标签:
【中文标题】使用嵌套循环提高 SQL 查询的性能 - PostgreSQL【英文标题】:Improve performance on SQL query with Nested Loop - PostgreSQL 【发布时间】:2016-07-26 11:16:06 【问题描述】:我正在使用 PostgreSQL,但我的 SQL 查询有一个奇怪的问题。 取决于我使用的日期参数。我的请求没有做同样的操作。
这是我的工作查询:
SELECT DISTINCT app.id_application
FROM stat sj
LEFT OUTER JOIN groupe gp ON gp.id_groupe = sj.id_groupe
LEFT OUTER JOIN application app ON app.id_application = gp.id_application
WHERE date_stat >= '2016/3/01'
AND date_stat <= '2016/3/31'
AND ( date_stat = date_gen-1 or (date_gen = '2016/04/01' AND date_stat = '2016/3/31'))
AND app.id_application IS NOT NULL
这个查询大约需要 2 秒(这对我来说没问题,因为我有很多行)。当我为此查询运行 EXPLAIN ANALYZE 时,我有这个:
HashAggregate (cost=375486.95..375493.62 rows=667 width=4) (actual time=2320.541..2320.656 rows=442 loops=1)
-> Hash Join (cost=254.02..375478.99 rows=3186 width=4) (actual time=6.144..2271.984 rows=263274 loops=1)
Hash Cond: (gp.id_application = app.id_application)
-> Hash Join (cost=234.01..375415.17 rows=3186 width=4) (actual time=5.926..2200.671 rows=263274 loops=1)
Hash Cond: (sj.id_groupe = gp.id_groupe)
-> Seq Scan on stat sj (cost=0.00..375109.47 rows=3186 width=8) (actual time=3.196..2068.357 rows=263274 loops=1)
Filter: ((date_stat >= '2016-03-01'::date) AND (date_stat <= '2016-03-31'::date) AND ((date_stat = (date_gen - 1)) OR ((date_gen = '2016-04-01'::date) AND (date_stat = '2016-03-31'::date))))
Rows Removed by Filter: 7199514
-> Hash (cost=133.45..133.45 rows=8045 width=12) (actual time=2.677..2.677 rows=8019 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 345kB
-> Seq Scan on groupe gp (cost=0.00..133.45 rows=8045 width=12) (actual time=0.007..1.284 rows=8019 loops=1)
-> Hash (cost=11.67..11.67 rows=667 width=4) (actual time=0.206..0.206 rows=692 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 25kB
-> Seq Scan on application app (cost=0.00..11.67 rows=667 width=4) (actual time=0.007..0.101 rows=692 loops=1)
Filter: (id_application IS NOT NULL)
Total runtime: 2320.855 ms
现在,当我尝试对当月进行相同的查询时(我们是 4 月 6 日,所以我正在尝试获取 4 月的所有 application_id)使用相同的查询
SELECT DISTINCT app.id_application
FROM stat sj
LEFT OUTER JOIN groupe gp ON gp.id_groupe = sj.id_groupe
LEFT OUTER JOIN application app ON app.id_application = gp.id_application
WHERE date_stat >= '2016/04/01'
AND date_stat <= '2016/04/30'
AND ( date_stat = date_gen-1 or ( date_gen = '2016/05/01' AND date_job = '2016/04/30'))
AND app.id_application IS NOT NULL
此查询现在需要 120 秒。所以我也在这个查询上运行了 EXPLAIN ANALYZE,现在它没有相同的操作:
HashAggregate (cost=375363.50..375363.51 rows=1 width=4) (actual time=186716.468..186716.532 rows=490 loops=1)
-> Nested Loop (cost=0.00..375363.49 rows=1 width=4) (actual time=1.945..186619.404 rows=118990 loops=1)
Join Filter: (gp.id_application = app.id_application)
Rows Removed by Join Filter: 82222090
-> Nested Loop (cost=0.00..375343.49 rows=1 width=4) (actual time=1.821..171458.237 rows=118990 loops=1)
Join Filter: (sj.id_groupe = gp.id_groupe)
Rows Removed by Join Filter: 954061820
-> Seq Scan on stat sj (cost=0.00..375109.47 rows=1 width=8) (actual time=0.235..1964.423 rows=118990 loops=1)
Filter: ((date_stat >= '2016-04-01'::date) AND (date_stat <= '2016-04-30'::date) AND ((date_stat = (date_gen - 1)) OR ((date_gen = '2016-05-01'::date) AND (date_stat = '2016-04-30'::date))))
Rows Removed by Filter: 7343798
-> Seq Scan on groupe gp (cost=0.00..133.45 rows=8045 width=12) (actual time=0.002..0.736 rows=8019 loops=118990)
-> Seq Scan on application app (cost=0.00..11.67 rows=667 width=4) (actual time=0.003..0.073 rows=692 loops=118990)
Filter: (id_application IS NOT NULL)
Total runtime: 186716.635 ms
因此,我决定通过减少查询中的条件数量,直到性能再次可以接受,来搜索问题出在哪里。
所以只有这个参数
WHERE date_stat >= '2016/04/01'
只需要 1.9 秒(就像第一个工作查询一样) 它还使用 2 个参数:
WHERE date_stat >= '2016/04/01'
AND app.id_application IS NOT NULL
但是当我尝试添加其中一行时,我在解释中有嵌套循环
AND date_stat <= '2016/04/30'
AND ( date_stat = date_gen-1 or ( date_gen = '2016/05/01' AND date_stat = '2016/04/30'))
有人知道它的来源吗?
【问题讨论】:
对两个查询执行EXPLAIN ANALYZE
并将两个输出添加到您的问题中。你还有那些表上的索引吗?
可能是数据分布问题,更改日期意味着优化器必须扫描更多行
@Mihai 或者优化器“thiks”有更多的行。所以最好给我们EXPLAIN ANALYZE
输出(并在表上执行 VACUUM ANALYZE)。因为如您所知,就数据库查询优化而言,有很多“可以”、“将”和“可能”。
根据需要使用完整的 EXPLAIN ANALYZE 进行编辑。这很奇怪,因为我有 3 月的完整数据,而我只有 4 月的数据(因为我们是 6 日)。它应该更快而不是更慢
问题在于,在第二个查询中,Postgres 完全低估了该条件返回的行数(估计:1 行,实际:118990 行)。所以这看起来像过时的统计信息(在第一个查询中,行数也被低估了,但这不会导致错误的计划)。检查运行 analyze stat;
是否有任何改变。看起来好像您在stat (date_stat)
上没有索引。创建一个也应该有所帮助。
【参考方案1】:
好的,优化器估计似乎有问题。他认为四月份只有1 row
,所以他选择NESTED LOOP
,这对于大量行来说效率非常低(在这种情况下为118,990
)。
-
对每个表执行
VACUUM ANALYZE
。这将清理死元组并刷新统计信息。
考虑添加基于dates
的索引,例如CREATE INDEX date_stat_idx ON <table with date_stat> USING btree (date_stat);
重新运行查询,
【讨论】:
我做了一个 VACUM ANALYZE(只有 1 C),它提高了性能(从 120 秒到 10 秒)。我现在将尝试使用 Index 来不断提高性能,谢谢。 请注意,实际上您只需要 ANALYZE 命令,如果不需要,则不必对表进行 VACUUM(抽真空很好,但可能比分析操作花费更长的时间) .以上是关于使用嵌套循环提高 SQL 查询的性能 - PostgreSQL的主要内容,如果未能解决你的问题,请参考以下文章
SQL中使用WITH AS提高性能-使用公用表表达式(CTE)简化嵌套SQL