簡體   English   中英

為什么這個簡單的查詢不使用 postgres 中的索引?

[英]Why does this simple query not use the index in postgres?

在我的 postgreSQL 數據庫中,我有一個名為"product"的表。 在此表中,我有一個名為"date_touched"的列,類型為timestamp 我在此列上創建了一個簡單的 btree 索引。 這是我的表的架構(我省略了不相關的列和索引定義):

                                           Table "public.product"
          Column           |           Type           | Modifiers                              
---------------------------+--------------------------+-------------------
 id                        | integer                  | not null default nextval('product_id_seq'::regclass)
 date_touched              | timestamp with time zone | not null

Indexes:
    "product_pkey" PRIMARY KEY, btree (id)
    "product_date_touched_59b16cfb121e9f06_uniq" btree (date_touched)

該表有 ~300,000 行,我想從按"date_touched"排序的表中獲取第 n 個元素。 當我要獲取第1000個元素時,需要0.2s,但是當我要獲取第100,000個元素時,大約需要6s。 我的問題是,為什么我已經定義了 btree 索引,但檢索第 100,000 個元素需要花費太多時間?

這是我的explain analyze查詢,顯示 postgreSQL 不使用 btree 索引而是對所有行進行排序以查找第 100,000 個元素:

  • 第一個查詢(第 100 個元素):
explain analyze
  SELECT product.id
  FROM product
  ORDER BY product.date_touched ASC
  LIMIT 1
  OFFSET 1000;
                                QUERY PLAN
-----------------------------------------------------------------------------------------------------
Limit  (cost=3035.26..3038.29 rows=1 width=12) (actual time=160.208..160.209 rows=1 loops=1)
->  Index Scan using product_date_touched_59b16cfb121e9f06_uniq on product  (cost=0.42..1000880.59 rows=329797 width=12) (actual time=16.651..159.766 rows=1001 loops=1)
Total runtime: 160.395 ms
  • 第二個查詢(第 100,000 個元素):
explain analyze
  SELECT product.id
  FROM product
  ORDER BY product.date_touched ASC
  LIMIT 1
  OFFSET 100000;
                           QUERY PLAN                         
------------------------------------------------------------------------------------------------------
 Limit  (cost=106392.87..106392.88 rows=1 width=12) (actual time=6621.947..6621.950 rows=1 loops=1)
   ->  Sort  (cost=106142.87..106967.37 rows=329797 width=12) (actual time=6381.174..6568.802 rows=100001 loops=1)
         Sort Key: date_touched
         Sort Method: external merge  Disk: 8376kB
         ->  Seq Scan on product  (cost=0.00..64637.97 rows=329797 width=12) (actual time=1.357..4184.115 rows=329613 loops=1)
 Total runtime: 6629.903 ms

這是一件非常好的事情,這里使用了 SeqScan。 您的OFFSET 100000對 IndexScan 來說不是一件好事。

一點理論

Btree 索引內部包含 2 個結構:

  1. 平衡樹和
  2. 鍵的雙鏈表。

第一個結構允許快速鍵查找,第二個結構負責排序。 對於更大的表,鏈接列表無法放入單個頁面,因此它是鏈接頁面的列表,其中每個頁面的條目保持順序,在索引創建期間指定。

但是,認為這些頁面一起位於磁盤上的想法是錯誤的。 事實上,它們更有可能分布在不同的位置。 為了根據索引的順序讀取頁面,系統必須執行隨機磁盤讀取。 與順序訪問相比,隨機磁盤 IO 是昂貴的。 因此,好的優化器會更喜歡SeqScan

我強烈推薦“SQL Performance Explained”一書以更好地理解索引。 它也可以在線獲得

到底是怎么回事?

您的OFFSET子句會導致數據庫讀取索引的鍵鏈接列表(導致大量隨機磁盤讀取),而不是丟棄所有這些結果,直到您達到想要的偏移量。 事實上,Postgres 決定在這里使用SeqScan + Sort是件好事——這應該會更快。

您可以通過以下方式檢查此假設:

  • 運行你的 big- OFFSET查詢的EXPLAIN (analyze, buffers)
  • SET enable_seqscan TO 'off';
  • 然后再次運行EXPLAIN (analyze, buffers) ,比較結果。

一般來說,最好避免使用OFFSET ,因為 DBMS 在這里並不總是選擇正確的方法。 (順便說一句,您使用的是哪個版本的 PostgreSQL?)下面是它對不同偏移值的執行情況的比較


編輯:為了避免OFFSET ,必須將分頁基於表中存在的真實數據,並且是索引的一部分。 對於這種特殊情況,可能會出現以下情況:

  • 顯示前 N(比如 20)個元素
  • 包括頁面上顯示的所有“下一步”鏈接的最大date_touched 您可以在應用程序端計算此值。 對“Previous”鏈接執行類似的操作,除了為這些鏈接包含最小的date_touch
  • 在服務器端,您將獲得限制值。 因此,對於“下一個”案例,您可以執行如下查詢:
SELECT id
  FROM product
 WHERE date_touched > $max_date_seen_on_the_page
 ORDER BY date_touched ASC
 LIMIT 20;

此查詢充分利用了索引。

當然,您可以根據需要調整此示例。 我使用分頁,因為它是OFFSET的典型案例。

還有一點要注意——多次查詢 1 行,將每個查詢的偏移量增加 1,這比執行返回所有這些記錄的單個批查詢要耗時得多,然后從應用程序端迭代這些記錄。

暫無
暫無

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

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