在阅读PostgreSQL查询计划时,如何“更好地思考”?

时间:2010-02-25 20:40:50

标签: postgresql sql-execution-plan

我今天花了一个多小时在一个我无法理解的查询计划上迷惑自己。查询是UPDATE,它根本就不会运行。完全陷入僵局:pg_locks表明它也没有等待任何事情。现在,我不认为自己是最好或最差的查询计划读者,但我发现这个非常困难。我想知道如何阅读这些?是否有一种方法可以遵循Pg aces来查明错误?

我打算提出另一个问题,如何解决这个问题,但现在我正在具体谈论如何阅读这些类型的计划

                                         QUERY PLAN                                         
--------------------------------------------------------------------------------------------
 Nested Loop Anti Join  (cost=47680.88..169413.12 rows=1 width=77)
   Join Filter: ((co.fkey_style = v.chrome_styleid) AND (co.name = o.name))
   ->  Nested Loop  (cost=5301.58..31738.10 rows=1 width=81)
         ->  Hash Join  (cost=5301.58..29722.32 rows=229 width=40)
               Hash Cond: ((io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text))
               ->  Seq Scan on options io  (cost=0.00..20223.32 rows=23004 width=36)
                     Filter: (name IS NULL)
               ->  Hash  (cost=4547.33..4547.33 rows=36150 width=24)
                     ->  Seq Scan on vehicles iv  (cost=0.00..4547.33 rows=36150 width=24)
                           Filter: (date_sold IS NULL)
         ->  Index Scan using options_pkey on options co  (cost=0.00..8.79 rows=1 width=49)
               Index Cond: ((co.fkey_style = iv.chrome_styleid) AND (co.code = io.code))
   ->  Hash Join  (cost=42379.30..137424.09 rows=16729 width=26)
         Hash Cond: ((v.lot_id = o.lot_id) AND ((v.vin)::text = (o.vin)::text))
         ->  Seq Scan on vehicles v  (cost=0.00..4547.33 rows=65233 width=24)
         ->  Hash  (cost=20223.32..20223.32 rows=931332 width=44)
               ->  Seq Scan on options o  (cost=0.00..20223.32 rows=931332 width=44)
(17 rows)

这个查询计划的问题 - 我相信我理解 - 可能最好用RhodiumToad irc://irc.freenode.net/#postgresql(他在这方面做得更好,所以我敢打赌他的解释更好)Nested Loop Anti Join

  哦,这个计划可能是灾难性的   该计划的问题在于它为每一行运行了一个非常昂贵的hashjoin   问题是来自其他连接的rows = 1估计值   规划人员认为可以在nestloop的内部路径中放置一个非常昂贵的查询,其中外部路径估计只返回一行。   很明显,按照规划师的估计,昂贵的部分只会运行一次   但这在实践中有明显的混乱倾向   问题在于规划者相信自己的估计   理想情况下,规划人员需要知道“估计返回1行”和“不可能返回超过1行”之间的区别   但它根本不清楚如何将其纳入现有代码

他接着说:

  

它可以影响任何连接,但通常最有可能加入子查询

现在,当我读到这个计划时,我注意到的第一件事是169,413,这需要花费Nested Loop(我会坚持上限)。此反加入分解为31,738的结果,费用为Hash Join,结果为137,424,费用为137,424。现在,31,738大于EXPLAIN ANALYZE所以我知道问题是哈希加入。

然后我继续查询之外的seq_scan哈希加入段。它在7秒内执行。我确定(lot_id,vin)和(co.code,和v.code)上有索引 - 有。我单独禁用了hashjoinrows="1",并注意到速度增加不到2秒。不足以说明一小时后它没有进展的原因。

但是,毕竟我完全错了!是的,它是查询的较慢部分,但因为Nested Loop Anti Join位(我认为它在RhodiumToad上)。这是规划师错误估计行数的错误(缺乏能力)?我怎么读这个来得出同样的结论rows="1"呢?

是否只是VACUUM FULL ANALYZE应该触发我解决这个问题?

我确实在所有涉及的表上运行{{1}},这是Postgresql 8.4。

2 个答案:

答案 0 :(得分:23)

看到这样的问题需要一些关于事情可能出错的经验。但是要在查询计划中查找问题,请尝试从内到外验证生成的计划,检查行数估计是否合理,成本估算是否与花费的时间相匹配。顺便说一句。两个成本估算不是下限和上限,第一个是产生第一排产出的估计成本,第二个是估计的总成本,详见explain documentation,还有一些{{3可用。它还有助于了解不同的访问方法如何工作。作为起点,维基百科有关于planner documentationnested loophash的信息。

在您的示例中,您将从:

开始
           ->  Seq Scan on options io  (cost=0.00..20223.32 rows=23004 width=36)
                 Filter: (name IS NULL)

运行EXPLAIN ANALYZE SELECT * FROM options WHERE name IS NULL;并查看返回的行是否与估算值匹配。 2关系通常不是问题,你试图发现数量级差异。

然后看EXPLAIN ANALYZE SELECT * FROM vehicles WHERE date_sold IS NULL;返回预期的行数。

然后上一级到哈希联接:

     ->  Hash Join  (cost=5301.58..29722.32 rows=229 width=40)
           Hash Cond: ((io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text))

查看EXPLAIN ANALYZE SELECT * FROM vehicles AS iv INNER JOIN options io ON (io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text) WHERE iv.date_sold IS NULL AND io.name IS NULL;是否会产生229行。

再增加一个级别INNER JOIN options co ON (co.fkey_style = iv.chrome_styleid) AND (co.code = io.code),预计只返回一行。这可能是问题所在,因为如果行的实际数字从1变为100,则遍历包含嵌套循环的内循环的总成本估算值将减少100倍。

规划者所犯的潜在错误可能是它期望加入co的两个谓词相互独立并使其选择性倍增。实际上它们可能是高度相关的,选择性更接近MIN(s1,s2)而不是s1 * s2。

答案 1 :(得分:2)

你在分析桌子了吗? pg_stats对这些表有什么看法?查询计划基于统计数据,这些必须是正常的。你用的是什么版本? 8.4

可以使用统计信息成本常量的统计信息,重新映射的数量,行数和postgresql.conf中的设置来计算成本。

work_mem也参与其中,它可能太低并迫使策划人员进行seqscan,以杀死性能......