【问题标题】:Why is postgres no longer using the index if I use another ORDER BY?如果我使用另一个 ORDER BY,为什么 postgres 不再使用索引?
【发布时间】:2021-05-25 15:16:44
【问题描述】:

我在这里没有找到我的问题的答案,所以我想我会问:

我有大约 18kk 行的下表:

# SELECT COUNT(1) from report;
  count   
----------
 18090892
(1 row)

# \d report
                          Table "public.report"
   Column    |           Type           | Collation | Nullable | Default 
-------------+--------------------------+-----------+----------+---------
 reporter_id | uuid                     |           | not null | 
 parsed      | boolean                  |           | not null | 
 id          | text                     |           | not null | 
 request_id  | uuid                     |           |          | 
 created     | timestamp with time zone |           | not null | now()
 customer    | text                     |           | not null | 
 subject     | text                     |           |          | 
Indexes:
    "PK_99e4d0bea58cba73c57f935a546" PRIMARY KEY, btree (id)
    "idx_report_created_desc" btree (created DESC)
    "idx_report_reporter_id_asc_created_desc" btree (reporter_id, created DESC)
    "idx_report_request_id_asc_created_desc" btree (request_id, created DESC)
Foreign-key constraints:
    "FK_5b809608bb38d119333b69f65f9" FOREIGN KEY (request_id) REFERENCES request(id)
    "FK_d41df66b60944992386ed47cf2e" FOREIGN KEY (reporter_id) REFERENCES reporter(id)

如果我使用ORDER BY created DESC LIMIT 25,则使用索引:

# EXPLAIN ANALYZE SELECT * FROM report ORDER BY created DESC LIMIT 25;
                                                                      QUERY PLAN                          
                                            
----------------------------------------------------------------------------------------------------------
--------------------------------------------
 Limit  (cost=0.44..2.49 rows=25 width=169) (actual time=0.035..0.063 rows=25 loops=1)
   ->  Index Scan using idx_report_created_desc on report  (cost=0.44..1482912.16 rows=18090892 width=169)
 (actual time=0.033..0.051 rows=25 loops=1)
 Planning Time: 0.239 ms
 Execution Time: 0.105 ms
(4 rows)

但是,如果我使用ORDER BY created DESC, id ASC LIMIT 25,则不再使用索引:

# EXPLAIN ANALYZE SELECT * FROM "report" ORDER BY "created" DESC, "id" ASC LIMIT 25;
                                                                   QUERY PLAN                                                                    
-------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=587891.07..587893.99 rows=25 width=169) (actual time=2719.606..2726.355 rows=25 loops=1)
   ->  Gather Merge  (cost=587891.07..2346850.67 rows=15075744 width=169) (actual time=2711.873..2718.618 rows=25 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Sort  (cost=586891.04..605735.72 rows=7537872 width=169) (actual time=2643.445..2643.448 rows=21 loops=3)
               Sort Key: created DESC, id
               Sort Method: top-N heapsort  Memory: 35kB
               Worker 0:  Sort Method: top-N heapsort  Memory: 32kB
               Worker 1:  Sort Method: top-N heapsort  Memory: 31kB
               ->  Parallel Seq Scan on report  (cost=0.00..374177.72 rows=7537872 width=169) (actual time=0.018..1910.204 rows=6030297 loops=3)
 Planning Time: 0.396 ms
 JIT:
   Functions: 1
   Options: Inlining true, Optimization true, Expressions true, Deforming true
   Timing: Generation 4.757 ms, Inlining 0.172 ms, Optimization 5.053 ms, Emission 2.003 ms, Total 11.985 ms
 Execution Time: 2731.226 ms
(16 rows)

如果我理解正确,应该仍然使用索引,因为应该返回相同的结果集,只是可能以 ORDER BY id ASC 确定的不同顺序。

所以我想知道为什么 postgres 决定进行并行 seq 扫描而不是使用索引然后按 id 对返回的 25 行进行排序?这肯定比并行 seq 扫描要快,不是吗?

或者我哪里错了?

【问题讨论】:

    标签: postgresql indexing sql-order-by


    【解决方案1】:

    PostgreSQL 并不是无限聪明的。有些事情它没有弄清楚,即使理论上它们是可能被弄清楚的。

    但它一直在变得越来越聪明。升级到版本 13,看看会发生什么。它应该使用索引扫描加上非常快速的“增量排序”。增量排序只需要打破“已创建”之间的联系,我假设这种情况很少见。

    如果我理解正确,应该仍然使用索引,因为应该返回相同的结果集,只是可能以由 ORDER BY id ASC 确定的不同顺序

    但是在存在 LIMIT 的情况下,以不同的顺序返回结果意味着返回不同结果的可能性。因此,它必须采取特殊措施来解决这个问题。在 v13 中是这样的。

    【讨论】:

    • 啊,好吧,有道理,感谢您对 v13 的见解!我有时间会检查一下。现在,我将只使用多列索引。再次感谢您的回答!
    【解决方案2】:

    这里最可能的解释是您目前只有created DESC 的索引。因此,当您执行以下查询时:

    SELECT * FROM report ORDER BY created DESC, id
    

    单列created 索引的叶节点没有可用的id 值。如果 Postgres 要使用这个索引,它必须为每个叶节点寻找原始表(聚集索引)。这种来回查找的成本可能会很高,有时甚至会超过首先使用索引的好处。

    如果你需要这样的两层排序,那么添加一个覆盖它的索引:

    CREATE INDEX idx_new ON report (created DESC, id);
    

    请注意,假设 id 是该表的主键,某些数据库(例如 MySQL)会自动将 id 列标记为您当前的 created DESC 索引。但在 Postgres 中似乎并非如此。

    【讨论】:

    • 是的,从\d report 的输出中可以看出,我在(created DESC) 上只有一个相关索引,在(created DESC, id) 上没有。但问题是为什么我什至需要一个多列索引,因为据我了解查询,它应该能够在创建时使用单列索引。
    • 您接受的答案并没有真正回答您的问题。
    • 为什么不呢?我问为什么在我的情况下它不使用单列索引,答案是因为 postgres 还不支持它(“postgres 不是无限智能”)。至少不是我正在使用的 v12.6。我有时间会检查 v13。
    • @ekzyis 您可以针对 any 数据库提出问题,而我的回答同样有效。您的问题的答案是您拥有的单列 B 树索引对 id 列一无所知。这使得索引对于 2 步排序不是很有帮助。也许 Postgres 有一些即将推出的增强功能。您接受的答案基本上是“Postgres 不使用索引,因为它不使用索引。”
    猜你喜欢
    • 1970-01-01
    • 2017-03-14
    • 1970-01-01
    • 2023-01-26
    • 2015-08-10
    • 2012-11-09
    • 2019-06-20
    • 1970-01-01
    • 2015-10-18
    相关资源
    最近更新 更多