Postgresql 不同服务器上的不同查询计划

Posted

技术标签:

【中文标题】Postgresql 不同服务器上的不同查询计划【英文标题】:Postgresql different query plans on different servers 【发布时间】:2017-02-13 10:54:30 【问题描述】:

总结:在 Postgres 9.3.15 上,我的开发机器和生产机器上的相同查询有非常不同的查询计划,生产机器慢了 300 倍!

我意识到 Postgresql 中的“限制”和“偏移”不是很好,但这并不能解释为什么它在我的开发中很快而在我的生产中很慢。

有什么建议吗?我试过改变 cpu_tuple_cost(0.1 到 0.5 - 没有帮助)


我的生产服务器(Azure:4 cpu,16gig ram)运行这个查询需要 1100 毫秒:

prod=# SELECT  "designs".* FROM "designs"  WHERE "designs"."user_id" IN (SELECT "users"."id" FROM "users"  WHERE (code_id=393))  ORDER BY updated_at desc, "designs"."updated_at" DESC LIMIT 20 OFFSET 0;
Time: 1175.486 ms

与此同时,我的开发服务器(Virtualbox、笔记本电脑、2 g ram)在同一个数据库上运行同一个查询需要 4 毫秒。

dev=# SELECT  "designs".* FROM "designs"  WHERE "designs"."user_id" IN (SELECT "users"."id" FROM "users"  WHERE (code_id=393))  ORDER BY updated_at desc, "designs"."updated_at" DESC LIMIT 20 OFFSET 0;
Time: 4.249 ms

生产查询计划是这样的:

prod=# explain  SELECT  "designs".* FROM "designs"  WHERE "designs"."user_id" IN (SELECT "users"."id" FROM "users"  WHERE (code_id=393))  ORDER BY updated_at desc, "designs"."updated_at" DESC LIMIT 20 OFFSET 0;
                                                          QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=169.00..113691.20 rows=20 width=966)
   ->  Nested Loop Semi Join  (cost=169.00..51045428.02 rows=8993 width=966)
         ->  Index Scan Backward using design_modification_date_idx on designs  (cost=85.00..1510927.32 rows=538151 width=966)
         ->  Index Scan using "User_UserUID_key" on users  (cost=84.00..92.05 rows=1 width=4)
               Index Cond: (id = designs.user_id)
               Filter: (code_id = 393)
(6 rows)

Time: 1.165 ms

开发查询计划是这样的:

dev=# explain SELECT  "designs".* FROM "designs"  WHERE "designs"."user_id" IN (SELECT "users"."id" FROM "users"  WHERE (code_id=393))  ORDER BY updated_at desc, "designs"."updated_at" DESC LIMIT 20 OFFSET 0;
                                                QUERY PLAN
-----------------------------------------------------------------------------------------------------------
 Limit  (cost=5686.78..5686.83 rows=20 width=964)
   ->  Sort  (cost=5686.78..5689.41 rows=1052 width=964)
         Sort Key: designs.updated_at
         ->  Nested Loop  (cost=0.71..5658.79 rows=1052 width=964)
               ->  Index Scan using code_idx on users  (cost=0.29..192.63 rows=67 width=4)
                     Index Cond: (code_id = 393)
               ->  Index Scan using "Design_idx_owneruid" on designs  (cost=0.42..73.58 rows=16 width=964)
                     Index Cond: (user_id = users.id)
(8 rows)

Time: 0.736 ms

编辑:好的,在转储生产数据的新副本后,我发现查询计划器是相同的(所以这是一个数据问题 - 抱歉!)。查询仍然很慢,有什么想法可以改进它吗?我尝试在设计(updated_at,user_id)和用户(id,code_id)上添加索引无济于事

EXPLAIN (ANALYZE, BUFFERS) 的输出:


 Limit  (cost=0.72..10390.79 rows=20 width=962) (actual time=1485.810..22025.828 rows=20 loops=1)
   Buffers: shared hit=883264 read=164340
   ->  Nested Loop Semi Join  (cost=0.72..4928529.42 rows=9487 width=962) (actual time=1485.809..22025.809 rows=20 loops=1)
         Buffers: shared hit=883264 read=164340
         ->  Index Scan Backward using design_modification_date_idx on designs  (cost=0.42..1442771.50 rows=538270 width=962) (actual time=1.737..18444.598 rows=263043 loops=1)
               Buffers: shared hit=108266 read=149409
         ->  Index Scan using "User_UserUID_key" on users  (cost=0.29..6.48 rows=1 width=4) (actual time=0.012..0.012 rows=0 loops=263043)
               Index Cond: (id = designs.user_id)
               Filter: (code_id = 393)
               Rows Removed by Filter: 1
               Buffers: shared hit=774998 read=14931
 Total runtime: 22027.477 ms
(12 rows)

编辑:建议查询的附加说明

dev=# explain (analyze) SELECT designs.*
FROM designs
   JOIN (SELECT *
           FROM users
           WHERE code_id=393
           OFFSET 0
        ) users
      ON designs.user_id = users.id
ORDER BY updated_at desc
LIMIT 20;


 Limit  (cost=0.72..13326.65 rows=20 width=962) (actual time=2597.877..95734.152 rows=20 loops=1)
   ->  Nested Loop  (cost=0.72..6321154.70 rows=9487 width=962) (actual time=2597.877..95734.135 rows=20 loops=1)
         Join Filter: (designs.user_id = users.id)
         Rows Removed by Join Filter: 143621402
         ->  Index Scan Backward using design_modification_date_idx on designs  (cost=0.42..1410571.52 rows=538270 width=962) (actual time=0.024..5217.228 rows=263043 loops=1)
         ->  Materialize  (cost=0.29..1562.31 rows=608 width=4) (actual time=0.000..0.146 rows=546 loops=263043)
               ->  Subquery Scan on users  (cost=0.29..1559.27 rows=608 width=4) (actual time=0.021..1.516 rows=546 loops=1)
                     ->  Index Scan using code_idx on users users_1  (cost=0.29..1553.19 rows=608 width=602) (actual time=0.020..1.252 rows=546 loops=1)
                           Index Cond: (code_id = 393)
 Total runtime: 95734.353 ms
(10 rows)

【问题讨论】:

请张贴EXPLAIN (ANALYZE, BUFFERS) 输出。看起来数据库包含不同的数据。 你是对的 - 开发数据只是过时了几天,但将新的生产副本放到开发中会导致同样的缓慢。我尝试按照下面的@chris-travers 添加索引,但没有运气。我已将 EXPLAIN (ANALYZE, BUFFERS) 的输出添加到主帖的底部。 【参考方案1】:

这是我的阅读方式。再次分析和缓冲区可能会有所帮助,但在这里我不这么认为。

在您的开发数据库中,它希望找到 67 个用户,因此它首先选择这些用户,然后排序,然后进行限制和偏移。对于所查看的数据量,这很快。

在生产中,它假设每个 id 有一个用户并倒退,但每个用户的设计数量要多得多,因此它首先根据排序标准搜索设计,然后过滤用户。当您意识到它可以在找到 20 行后停止时,这是有道理的。但是数据统计使这成为一个糟糕的计划,您会得到一些检查一堆额外记录以找到相关记录的东西。

这就是我对正在发生的事情的猜测。在尝试修复之前,请确保您了解原因.....

现在,如果您要在用户表上创建 (user_id, code_id) 索引,您可能会获得显着的加速,因为您可以避免在索引扫描阶段检查元组。

另一种选择可能是在设计表上创建(modification_date, user_id) 的索引。然而,这对我来说似乎是一个更长远的目标。

【讨论】:

【参考方案2】:

问题是userscode_id = 393 大多与designs 和低updated_at 相关,因此PostgreSQL 必须在找到满足条件的20 行之前从designs 扫描263043 行。

由于 PostgreSQL 没有跨表统计,它不知道通过使用适当的索引来避免排序的想法会导致超出预期的扫描行数。

您可以使用 OFFSET 0 的旧而丑陋的技巧来重写查询,这不会改变查询语义,但会阻止 PostgreSQL 考虑有问题的优化:

SELECT designs.*
FROM designs
   JOIN (SELECT *
           FROM users
           WHERE code_id=393
           OFFSET 0  /* avoid optimizations beyond using an index for code_id */
        ) u
      ON designs.user_id = users.id
ORDER BY updated_at desc
LIMIT 20;

这应该会给你想要的快速计划。

如果这还不足以推动 PostgreSQL 选择好的计划,您可以通过删除 design_modification_date_idx 索引来进一步帮助它,如果这是一个选项。

【讨论】:

嗨 - 感谢您的建议,我试过了,但性能没有提高:/ 您对此查询的计划是什么?如果它类似于问题中的第二个计划,则表明 PostgreSQL 确实 选择了正确的计划。无论如何,如果您想让我看一下,请运行 EXPLAIN (ANALYZE) 并将结果添加到您的问题中。 您确定designsuser_id 上有索引吗? 是的 - 肯定有那个索引 嗯 - 我添加了另一个建议。如果这没有帮助,我就别无选择了。

以上是关于Postgresql 不同服务器上的不同查询计划的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL学习系列—EXPLAIN ANALYZE查询计划解读

具有复杂查询和相当大数据集的本地服务器上的 PostgreSQL 超时

Postgresql 查询的过滤条件中的列上的字符串操作如何影响它选择的计划

PostgreSQL比较不同服务器上的数据[重复]

Postgresql 计划不周的查询运行时间过长

是否可以在不同物理服务器上的 oracle 数据库和 postgresql 数据库之间使用数据库链接