19

今天我花了一个多小时对一个我无法理解的查询计划感到困惑。该查询是一个UPDATE,它根本不会运行。完全陷入僵局:pg_locks表明它也没有等待任何东西。现在,我不认为自己是最好或最差的查询计划阅读器,但我发现这一点异常困难。我想知道如何阅读这些内容?是否有 Pg ace 遵循的方法来查明错误?

我打算就如何解决这个问题提出另一个问题,但现在我要专门讨论如何阅读这些类型的计划

                                         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

哦,该计划可能是灾难性的,该计划的问题是它为每一行运行一个非常昂贵的 hashjoin 问题是来自另一个连接的 rows=1 估计,并且计划者认为可以在内部放置一个非常昂贵的查询估计外部路径仅返回一行的嵌套循环的路径。因为,显然,根据规划者的估计,昂贵的部分只会运行一次,但这在实践中很明显会搞砸,问题是规划者理想地相信自己的估计,规划者需要知道“估计”之间的区别返回 1 行”和“不可能返回超过 1 行”,但目前尚不清楚如何将其合并到现有代码中

他接着说:

它可以影响任何连接,但通常针对子查询的连接是最有可能的

现在,当我阅读这个计划时,我注意到的第一件事是Nested Loop Anti Join,这有一个成本169,413(我会坚持上限)。此反连接分解Nested Loop为成本为的结果和成本为31,738的结果。现在,,比我知道问题出在 Hash Join 的要大得多。Hash Join137,424137,42431,738

然后我继续EXPLAIN ANALYZE查询之外的 Hash Join 段。它在 7 秒内执行。我确保 (lot_id, vin) 和 (co.code, and v.code) 上有索引——有。我单独禁用seq_scanhashjoin注意到速度增加不到 2 秒。还不足以解释为什么一个小时后它没有进展。

但是,毕竟我完全错了!是的,这是查询中较慢的部分,但因为rows="1"位(我认为它在Nested Loop Anti Join)。这是计划程序错误估计行数的错误(缺乏能力)?我应该如何阅读这个来得出相同的结论RhodiumToad

仅仅rows="1"是这应该触发我弄清楚这一点吗?

我确实VACUUM FULL ANALYZE在所有涉及的表上运行,这是 Postgresql 8.4。

4

2 回答 2

23

看穿这样的问题需要一些关于哪里可能出错的经验。但是要找到查询计划中的问题,请尝试从内到外验证生成的计划,检查行数估计是否合理,成本估计是否与花费的时间相匹配。顺便提一句。这两个成本估算没有上下限,第一个是生产第一行输出的估算成本,第二个数字是估算的总成本,详细信息请参阅说明文档,还有一些可用的计划文档。它还有助于了解不同的访问方法是如何工作的。作为起点,Wikipedia 提供了有关嵌套循环散列合并连接的信息。

在您的示例中,您将从:

           ->  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 倍。

The underlying mistake that the planner is making is probably that it expects that the two predicates for joining in co are independent of each other and multiplies their selectivities. While in reality they may be heavily correlated and the selectivity is closer to MIN(s1, s2) not s1*s2.

于 2010-02-26T12:19:07.367 回答
2

你分析表格了吗?pg_stats 对这些表有什么看法?查询计划基于统计信息,这些必须没问题。你用什么版本?8.4?

可以使用统计信息、相关页数、行数和 postgresql.conf 中的 Planner Cost Constants 设置来计算成本。

work_mem 也参与其中,它可能太低并迫使规划者进行 seqscan,以扼杀性能......

于 2010-02-25T21:23:31.473 回答