簡體   English   中英

多個 ORDER BY DESC 不會在 Postgres 中使用索引

[英]Multiple ORDER BY DESC will not use index in Postgres

我正在嘗試創建一些查詢,以便在 Postgres 上實現 cursor 分頁(類似於: https://shopify.engineering/pagination-relative-cursors )。 在我的實現中,即使對非唯一列進行排序,我也試圖實現高效的分頁。

我正在努力有效地做到這一點,特別是在給定特定 cursor 檢索上一頁的查詢上。

我用來測試這些查詢的表(>3M 記錄)非常簡單,它具有以下結構:

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

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

這是我用於檢索下一頁的查詢,給定由最新 ID 和最新 assetId 組成的 cursor:

 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

您的第一個查詢的快速性能似乎取決於您的常量“CURSOR_ASSETID”在該列的分布中的位置。 或者也許這種運氣不是運氣而是它會一直如此嗎?

為了更普遍地獲得良好的性能,包括反向排序,您需要使用元組比較器而不是 OR 比較器來編寫查詢。

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

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

並且沒有理由用 DESC 修飾索引,因為 PostgreSQL 知道如何向后讀取索引。 當兩列的順序彼此不同時,需要裝飾索引,因為那時您需要“螺旋式”讀取未裝飾的索引,而不是完全向前或完全向后讀取。 (但這在這里無論如何都行不通,因為元組比較器在列之間不能有不同的順序。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM