sql - 带有 ORDER 和 LIMIT 子句的极慢 PostgreSQL 查询

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

我有一张表,我们称它为“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)

最佳答案

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

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

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

关于sql - 带有 ORDER 和 LIMIT 子句的极慢 PostgreSQL 查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6037843/

相关文章:

mysql - 了解 MySQL 查询优化器

Oracle 参数化查询性能

mysql - SQL查询的时间戳计算

sql - 我如何获取同名员工的所有ID

postgresql - 在postgres下将PreparedStatement参数设置为null的正确方法

django - 如何在我的 Django 应用程序中添加远程 postgres 数据库?

sql - ASP.NET 将重复结果加入一个并对其他字段求和

mysql - WHERE 子句后跟 JOIN

javascript - select 返回零行,尽管它应该返回一些条目

MySQL:针对一组数据的类似分区函数?