【问题标题】:Extremely slow PostgreSQL query with ORDER and LIMIT clauses带有 ORDER 和 LIMIT 子句的极慢 PostgreSQL 查询
【发布时间】:2023-03-12 03:46:01
【问题描述】:

我有一张表,我们称之为“foos”,里面有将近 600 万条记录。我正在运行以下查询:

SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
ORDER BY "foos"."id" DESC
LIMIT 5 OFFSET 0;

此查询需要很长时间才能运行(Rails 在运行时超时)。有问题的所有 ID 都有一个索引。奇怪的是,如果我删除 ORDER BY 子句或 LIMIT 子句,它几乎会立即运行。

我假设ORDER BYLIMIT 的存在使PostgreSQL 在查询计划中做出了一些错误的选择。有人对如何解决此问题有任何想法吗?

如果有帮助,这里是所有 3 个案例的 EXPLAIN

//////// Both ORDER and LIMIT
SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
ORDER BY "foos"."id" DESC
LIMIT 5 OFFSET 0;
                                                     QUERY PLAN                                                     
--------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..16663.44 rows=5 width=663)
   ->  Nested Loop  (cost=0.00..25355084.05 rows=7608 width=663)
         Join Filter: (foos.bar_id = bars.id)
         ->  Index Scan Backward using foos_pkey on foos  (cost=0.00..11804133.33 rows=4963477 width=663)
               Filter: (((NOT privacy_protected) OR (user_id = 67962)) AND ((status)::text = 'DONE'::text))
         ->  Materialize  (cost=0.00..658.96 rows=182 width=4)
               ->  Index Scan using index_bars_on_baz_id on bars  (cost=0.00..658.05 rows=182 width=4)
                     Index Cond: (baz_id = 13266)
(8 rows)

//////// Just LIMIT
SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
LIMIT 5 OFFSET 0;
                                                              QUERY PLAN                                                               
---------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..22.21 rows=5 width=663)
   ->  Nested Loop  (cost=0.00..33788.21 rows=7608 width=663)
         ->  Index Scan using index_bars_on_baz_id on bars  (cost=0.00..658.05 rows=182 width=4)
               Index Cond: (baz_id = 13266)
         ->  Index Scan using index_foos_on_bar_id on foos  (cost=0.00..181.51 rows=42 width=663)
               Index Cond: (foos.bar_id = bars.id)
               Filter: (((NOT foos.privacy_protected) OR (foos.user_id = 67962)) AND ((foos.status)::text = 'DONE'::text))
(7 rows)

//////// Just ORDER
SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
ORDER BY "foos"."id" DESC;
                                                              QUERY PLAN                                                               
---------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=36515.17..36534.19 rows=7608 width=663)
   Sort Key: foos.id
   ->  Nested Loop  (cost=0.00..33788.21 rows=7608 width=663)
         ->  Index Scan using index_bars_on_baz_id on bars  (cost=0.00..658.05 rows=182 width=4)
               Index Cond: (baz_id = 13266)
         ->  Index Scan using index_foos_on_bar_id on foos  (cost=0.00..181.51 rows=42 width=663)
               Index Cond: (foos.bar_id = bars.id)
               Filter: (((NOT foos.privacy_protected) OR (foos.user_id = 67962)) AND ((foos.status)::text = 'DONE'::text))
(8 rows)

【问题讨论】:

  • 您的查询与您的查询计划不匹配。如果您需要帮助,至少提供完整的相关详细信息...
  • 对不同的查询/计划感到抱歉;我试图混淆一点,但回想起来,我不知道为什么。明天我会更新实际的查询和计划。
  • 这个问题在11.5中仍然存在,很奇怪,不是吗?

标签: sql postgresql query-optimization sql-order-by limit


【解决方案1】:

它可能正在“foos”上运行全表扫描。您是否尝试过更改表的顺序,而是使用左连接而不是内连接,看看它是否能更快地显示结果。

说...

SELECT "bars"."id", "foos".*
FROM "bars"
LEFT JOIN "foos" ON "bars"."id" = "foos"."bar_id"
WHERE "bars"."baz_id" = 13266
ORDER BY "foos"."id" DESC
LIMIT 5 OFFSET 0;

【讨论】:

    【解决方案2】:

    当您同时拥有 LIMIT 和 ORDER BY 时,优化器已决定通过键降序遍历 foo 上的未过滤记录,直到它获得其余条件的五个匹配项,这样会更快。在其他情况下,它只是将查询作为嵌套循环运行并返回所有记录。

    顺便说一句,我想说的问题是 PG 没有理解各种 id 的联合分布,这就是为什么计划如此不理想。

    对于可能的解决方案:我假设您最近运行了 ANALYZE。如果没有,请这样做。这可以解释为什么即使在快速返回的版本上,您的估计时间也很高。如果问题仍然存在,或许可以将 ORDER BY 作为子选择运行,并在外部查询中添加 LIMIT。

    【讨论】:

    • 好的...所以foos.bars.last 会在条形图上进行全索引扫描...不错 -_-
    • 好的...所以只有当 foos 有 0 条时才会进行完整的索引扫描...虽然仍然很烦人
    • 解决方案:foos.bars.last unless foos.bars.empty?...虽然还是很生气
    • @Jim 这里的lastempty 是什么意思:看起来不像SQL。
    • 对于任何在使用 Django 时遇到性能问题的人,这正是它默认使用方法 first():ORDER BY <pk> LIMIT 1 所做的事情。
    【解决方案3】:

    您的查询计划表明有一个过滤器

    (((NOT privacy_protected) OR (user_id = 67962)) AND ((status)::text = 'DONE'::text))
    

    哪个没有出现在 SELECT 中 - 它来自哪里?

    另外,请注意,表达式被列为“过滤器”而不是“索引条件”,这似乎表明没有应用索引。

    【讨论】:

    • 对此感到抱歉。我不知道为什么我试图混淆。我会在早上修好它。
    【解决方案4】:

    可能会发生这种情况,因为在它尝试订购然后选择之前。为什么不尝试在外部全选中对结果进行排序?就像是: SELECT * FROM (SELECT ... INNER JOIN ETC...) ORDER BY ... DESC

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-21
      • 2023-04-09
      相关资源
      最近更新 更多