【问题标题】:Multiple ORDER BY DESC will not use index in Postgres多个 ORDER BY DESC 不会在 Postgres 中使用索引
【发布时间】:2023-01-26 00:07:04
【问题描述】:

我正在尝试创建一些查询以在 Postgres 上实现游标分页(类似于:https://shopify.engineering/pagination-relative-cursors)。在我的实现中,我试图达到有效的分页,即使订购非唯一列.

我正在努力有效地做到这一点,特别是在给定特定光标的情况下检索上一页的查询。

我用来测试这些查询的表(>3M 记录)非常简单,它具有以下结构:

CREATE TABLE "placemarks" (
    "id" serial NOT NULL DEFAULT,
    "assetId" text,
    "createdAt" timestamptz,
    PRIMARY KEY ("id")
);

我在 id 字段上有一个清晰的索引,在 assetId 列上也有一个索引。

这是我在给定由最新 ID 和最新 assetId 组成的游标的情况下用于检索下一页的查询:

 SELECT
    *
FROM
    "placemarks"
WHERE
    "assetId" > 'CURSOR_ASSETID'
    or("assetId" = 'CURSOR_ASSETID'
        AND id > CURSOR_INT_ID)
ORDER BY
    "assetId",
    id
LIMIT 5;

这个查询实际上非常快,它使用索引并允许通过使用唯一 ID 字段处理 assetId 上的重复值,以避免跳过具有相同 CURSOR_ASSETID 值的重复行。

  ->  Sort  (cost=25709.62..25726.63 rows=6803 width=2324) (actual time=0.128..0.138 rows=5 loops=1)
"        Sort Key: ""assetId"", id"
        Sort Method: top-N heapsort  Memory: 45kB
        ->  Bitmap Heap Scan on placemarks  (cost=271.29..25596.63 rows=6803 width=2324) (actual time=0.039..0.088 rows=11 loops=1)
"              Recheck Cond: (((""assetId"")::text > 'CURSOR_ASSETID'::text) OR ((""assetId"")::text = 'CURSOR_ASSETID'::text))"
"              Filter: (((""assetId"")::text > 'CURSOR_ASSETID'::text) OR (((""assetId"")::text = 'CURSOR_ASSETID'::text) AND (id > CURSOR_INT_ID)))"
              Rows Removed by Filter: 1
              Heap Blocks: exact=10
              ->  BitmapOr  (cost=271.29..271.29 rows=6803 width=0) (actual time=0.030..0.034 rows=0 loops=1)
"                    ->  Bitmap Index Scan on ""placemarks_assetId_key""  (cost=0.00..263.45 rows=6802 width=0) (actual time=0.023..0.023 rows=11 loops=1)"
"                          Index Cond: ((""assetId"")::text > 'CURSOR_ASSETID'::text)"
"                    ->  Bitmap Index Scan on ""placemarks_assetId_key""  (cost=0.00..4.44 rows=1 width=0) (actual time=0.005..0.005 rows=1 loops=1)"
"                          Index Cond: ((""assetId"")::text = 'CURSOR_ASSETID'::text)"
Planning time: 0.201 ms
Execution time: 0.194 ms

问题是当我尝试获取相同的页面但查询应该返回上一页时:

SELECT
    *
FROM
    placemarks
WHERE
    "assetId" < 'CURSOR_ASSETID'
    or("assetId" = 'CURSOR_ASSETID'
        AND id < CURSOR_INT_ID)
ORDER BY
    "assetId" desc,
    id desc
LIMIT 5;

对于这个查询,没有使用索引,即使它会更快:

Limit  (cost=933644.62..933644.63 rows=5 width=2324)
  ->  Sort  (cost=933644.62..944647.42 rows=4401120 width=2324)
"        Sort Key: ""assetId"" DESC, id DESC"
        ->  Seq Scan on placemarks  (cost=0.00..860543.60 rows=4401120 width=2324)
"              Filter: (((""assetId"")::text < 'CURSOR_ASSETID'::text) OR (((""assetId"")::text = 'CURSOR_ASSETID'::text) AND (id < CURSOR_INT_ID)))"

我注意到,通过强制使用 SET enable_seqscan = OFF; 的索引,查询似乎正在使用索引并且它执行得更好更快。查询计划结果:

Limit  (cost=12.53..12.54 rows=5 width=108) (actual time=0.532..0.555 rows=5 loops=1)
  ->  Sort  (cost=12.53..12.55 rows=6 width=108) (actual time=0.524..0.537 rows=5 loops=1)
        Sort Key: assetid DESC, id DESC
        Sort Method: top-N heapsort  Memory: 25kB
"        ->  Bitmap Heap Scan on ""placemarks""  (cost=8.33..12.45 rows=6 width=108) (actual time=0.274..0.340 rows=14 loops=1)"
"              Recheck Cond: ((assetid < 'CURSOR_ASSETID'::text) OR (assetid = 'CURSOR_ASSETID'::text))"
"              Filter: ((assetid < 'CURSOR_ASSETID'::text) OR ((assetid = 'CURSOR_ASSETID'::text) AND (id < 14)))"
              Rows Removed by Filter: 1
              Heap Blocks: exact=1
              ->  BitmapOr  (cost=8.33..8.33 rows=7 width=0) (actual time=0.152..0.159 rows=0 loops=1)
"                    ->  Bitmap Index Scan on ""placemarks_assetid_idx""  (cost=0.00..4.18 rows=6 width=0) (actual time=0.108..0.110 rows=12 loops=1)"
"                          Index Cond: (assetid < 'CURSOR_ASSETID'::text)"
"                    ->  Bitmap Index Scan on ""placemarks_assetid_idx""  (cost=0.00..4.15 rows=1 width=0) (actual time=0.036..0.036 rows=3 loops=1)"
"                          Index Cond: (assetid = 'CURSOR_ASSETID'::text)"
Planning time: 1.319 ms
Execution time: 0.918 ms

优化第二个查询以始终使用索引的任何线索?

Postgres 数据库版本:10.20

【问题讨论】:

  • 如果第二个查询应该使用索引,则需要 (assetid desc, id desc) 上的索引
  • 它不适用于索引的反向扫描吗?
  • @a_horse_with_no_name 但您不会期望 WHERE 子句中的 OR 条件会使索引变得不那么有用或不可用吗?
  • @TimBiegeleisen:是的,我更关注order by 部分。
  • 所以主要问题是 WHERE 条件?

标签: sql postgresql sorting indexing pagination


【解决方案1】:

您的第一个查询的快速性能似乎取决于您的常量“CURSOR_ASSETID”在该列的分布中的位置。或者也许这种运气不是运气而是它会一直如此吗?

为了更普遍地获得良好的性能,包括反向排序,您需要使用元组比较器而不是 OR 比较器来编写查询。

WHERE
    ("assetId",id) < ('something',500000)

如果您使用的是在 v13 中引入增量排序之前的版本,或者如果“assetId”可以有大量的联系,那么您将需要在 ("assetId",id) 上使用多列索引以获得最佳性能。

【讨论】:

  • 谢谢!我已经尝试过使用元组比较器但没有运气......我正在使用 Postgres 10.20。即使使用多列索引,DESC 排序也有相同的行为,而在 ASC 排序中没有问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-06
  • 2015-02-26
  • 2021-07-14
  • 1970-01-01
相关资源
最近更新 更多