为啥主键上的“order by”会更改查询计划,从而忽略有用的索引?

Posted

技术标签:

【中文标题】为啥主键上的“order by”会更改查询计划,从而忽略有用的索引?【英文标题】:Why is "order by" on the primary key changing the query plan so that it ignores an useful index?为什么主键上的“order by”会更改查询计划,从而忽略有用的索引? 【发布时间】:2020-07-28 12:06:10 【问题描述】:

在调查了为什么多列索引无法像我预期的那样加快查询速度后,我意识到这是因为一个简单的 ORDER BY 子句。

我将查询简化为这种简单的形式(首先没有 ORDER BY,然后使用它):

somedb=# explain select * from user_resource where resource_id = 943 and status = 2 limit 10;
                                                    QUERY PLAN                                                     
-------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.56..39.29 rows=10 width=44)
   ->  Index Scan using user_resource_resource_id_status on user_resource  (cost=0.56..5422.22 rows=1400 width=44)
         Index Cond: ((resource_id = 943) AND (status = 2))
(3 rows)

Time: 0.409 ms
somedb=# explain select * from user_resource where resource_id = 943 and status = 2 order by id desc limit 10;
                                                          QUERY PLAN                                                          
------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=1000.46..4984.60 rows=10 width=44)
   ->  Gather Merge  (cost=1000.46..558780.31 rows=1400 width=44)
         Workers Planned: 2
         ->  Parallel Index Scan Backward using idx_121518_primary on user_resource  (cost=0.44..557618.69 rows=583 width=44)
               Filter: ((resource_id = 943) AND (status = 2))

添加 ORDER BY 后,您会看到 user_resource_resource_id_status 键不再使用,并且查询速度变慢了约 10 倍。

这是为什么?有没有办法解决它?我认为按一个简单的整数字段排序不应该使索引无用。谢谢。

【问题讨论】:

【参考方案1】:

limit相关。

您可以在没有限制子句且偏移量为 0 的情况下运行查询,以防止内联子查询,然后应用限制。

select * from (
   select * from user_resource 
   where resource_id = 943 
      and status = 2
   offset 0
) sub
 order by id desc 
limit 10;

【讨论】:

如果我删除它使用它的 LIMIT ,真的哇!我没想到会这样,对我来说这是违反直觉的。感谢您的解决方案。不确定我是否可以使用 ORM 轻松地将其调整为我的原始查询。需要这样的嵌套来使用索引似乎很奇怪。 这看起来像是一个糟糕的计划决策 - 计划者应该计算出使用正确的索引 + 排序比按排序顺序遍历索引 + 过滤成本更低,因此它应该选择第一个执行计划。 【参考方案2】:

这取决于您如何创建index。示例NULLS FIRSTASC, DESC, NULLS FIRST, and/or NULLS LAST

参考https://www.postgresql.org/docs/current/indexes-ordering.html,解释如何使用Indexes and ORDER BY

【讨论】:

以上是关于为啥主键上的“order by”会更改查询计划,从而忽略有用的索引?的主要内容,如果未能解决你的问题,请参考以下文章

合理使用Order by 重要性

使用主键和排序键上的两个条件查询表

为啥 MIN() 查询会比 ORDER BY X LIMIT 1 慢?

复合主键上的慢连接

Mysql 查询与 order by。奇怪的执行计划

为啥 eloquent 不会在不同的主键上返回错误?