在 postgres 中运行时查询计划更改

Posted

技术标签:

【中文标题】在 postgres 中运行时查询计划更改【英文标题】:Query plan changes at runtime in postgres 【发布时间】:2020-02-04 09:45:32 【问题描述】:

我有两个简单的表格:

Profile (20M entries)
----------------------------
pId | fail 
123 | 0
234 | 2
345 | 0

Work (50M entries)
-----------------
pId
123
234
123
345
123
345

我只想在Profile 表中将fail 标记为1,以获取Work 表中超过阈值pId 的条目。 Profile 表中的 pId 已编入索引,我不想触及 fail 不是 0 的行。

我现在使用的查询是:

UPDATE Profile 
SET fail = 1 
WHERE pId IN 
    (
      SELECT pId 
      FROM Work 
      GROUP BY pId 
      HAVING COUNT(*) > 2
    )
AND Profile.fail = 0;

在pgAdmin中,我得到如下解释计划:

"Update on Profile a  (cost=1134492.79..1559750.23 rows=5180 width=1014)"
"  ->  Hash Join  (cost=1134492.79..1559750.23 rows=5180 width=1014)"
"        Hash Cond: (a.pId = b.pId)"
"        ->  Seq Scan on Profile a  (cost=0.00..425216.00 rows=15462 width=976)"
"              Filter: (fail = 0)"
"        ->  Hash  (cost=1134491.95..1134491.95 rows=67 width=32)"
"              ->  Subquery Scan on b  (cost=1134488.78..1134491.95 rows=67 width=32)"
"                    ->  HashAggregate  (cost=1134488.78..1134491.28 rows=67 width=4)"
"                          Group Key: Work.pId"
"                          Filter: (count(*) > 5)"
"                          ->  Seq Scan on Work  (cost=0.00..894341.52 rows=48029452 width=4)"

运行需要几分钟。

现在当这两个表在运行时使用相同的数据创建时,查询计划变为:

"Update on Profile  (cost=1250747.42..1251317.47 rows=67 width=386)"
"  ->  Nested Loop  (cost=1250747.42..1251317.47 rows=67 width=386)"
"        ->  Subquery Scan on "ANY_subquery"  (cost=1250746.98..1250750.15 rows=67 width=32)"
"              ->  HashAggregate  (cost=1250746.98..1250749.48 rows=67 width=4)"
"                    Group Key: Work.pId"
"                    Filter: (count(*) > 5)"
"                    ->  Seq Scan on Work  (cost=0.00..985990.32 rows=52951332 width=4)"
"        ->  Index Scan using Profile_idx on Profile  (cost=0.44..8.46 rows=1 width=348)"
"              Index Cond: (pId = "ANY_subquery".pId)"
"              Filter: (fail = 0)"

运行需要一个小时。我什至尝试从子查询切换到连接,但它仍然产生相同的结果。任何帮助将不胜感激。

【问题讨论】:

首先,如果子查询中有很多结果,WHERE pId IN (subquery) 可能会非常慢。如果结果数量适中(可能不超过一百个左右,但这只是一个模糊的想法),那么使用数组可以大大提高性能:WHERE pId = ANY(ARRAY(subquery))。查询规划器通常会自动执行 INARRAY 的转换,但有时不会,必须明确完成。 @404 感谢您的评论。我在内部查询中返回了大约 20M 行。让我尝试使用ANY(ARRARY(subquery)) 并回复您。 哎呀!在这种情况下,我不会打扰数组。试试这个,看看性能如何:UPDATE profile p SET ... WHERE (SELECT COUNT(1) FROM work w WHERE w.pid = p.pid) > 2 AND fail = 0,动态计算每一行的计数可能会更快,希望使用work 上的索引,而不是创建一个包含 2000 万行的结果集和然后将每个profile 行与这个未索引的结果集进行比较。 EXPLAIN (ANALYZE, BUFFERS) 这两个查询都会有所帮助。创建表后你运行ANALYZE了吗? 我确实关联了表格,但它需要更多时间。让我再试一次。 @Laurenz 不,我没有,让我试试。 【参考方案1】:

你的问题的关键可能是:

现在当这两个表在运行时使用相同的数据创建时,查询计划变为[更糟]

PostgreSQL 会自动收集表统计信息,但自动分析需要很短的时间才能启动。

在批量数据修改和自动分析完成之间运行的所有查询都可能有错误的执行计划。

最好在大量数据修改结束时在表上显式运行ANALYZE

【讨论】:

如果我关闭嵌套循环和哈希连接,查询将在 4 分钟内运行。这是我在运行查询之前应该做的事情并在查询完成后打开它们吗? 那将是最后的手段;我相信你可以做得更好。如果您将EXPLAIN (ANALYZE, BUFFERS) 输出添加到问题中,我们可能会得到线索。 查询从最近 3 小时开始运行,仍未完成,所以很难得到analyze, buffers。但是根据您的建议,我已将统计信息设置为 25,这似乎选择了正确的查询计划并且只需要一分钟即可分析。感谢您的帮助。 我明白了。然后获取EXPLAIN (ANALYZE, BUFFERS) SELECT pId FROM Work GROUP BY pId HAVING COUNT(*) > 2; 会有所帮助,因为这可能就是问题所在。依靠低质量统计数据造成的错误估计来意外解决问题有点脆弱。

以上是关于在 postgres 中运行时查询计划更改的主要内容,如果未能解决你的问题,请参考以下文章

在 MFC 中运行时更改编辑框属性

在visual studio 2008中运行时,表单不显示更改

在 WPF .net core 5 中运行时更改应用程序文化时如何更新属性绑定

为啥在火花中运行时配置单元查询不起作用

在 C# 中运行时更改 Crystal Report 数据源(访问)

如何使用ng-model更改表中运行时的记录