我今天花了一个多小时在一个我无法理解的查询计划上迷惑自己。查询是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)上有索引 - 有。我单独禁用了hashjoin
和rows="1"
,并注意到速度增加不到2秒。不足以说明一小时后它没有进展的原因。
但是,毕竟我完全错了!是的,它是查询的较慢部分,但因为Nested Loop Anti Join
位(我认为它在RhodiumToad
上)。这是规划师错误估计行数的错误(缺乏能力)?我怎么读这个来得出同样的结论rows="1"
呢?
是否只是VACUUM FULL ANALYZE
应该触发我解决这个问题?
我确实在所有涉及的表上运行{{1}},这是Postgresql 8.4。
答案 0 :(得分:23)
看到这样的问题需要一些关于事情可能出错的经验。但是要在查询计划中查找问题,请尝试从内到外验证生成的计划,检查行数估计是否合理,成本估算是否与花费的时间相匹配。顺便说一句。两个成本估算不是下限和上限,第一个是产生第一排产出的估计成本,第二个是估计的总成本,详见explain documentation,还有一些{{3可用。它还有助于了解不同的访问方法如何工作。作为起点,维基百科有关于planner documentation,nested loop和hash的信息。
在您的示例中,您将从:
开始 -> 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)
可以使用统计信息成本常量的统计信息,重新映射的数量,行数和postgresql.conf中的设置来计算成本。
work_mem也参与其中,它可能太低并迫使策划人员进行seqscan,以杀死性能......