[英]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 個元素:
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
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 個結構:
第一個結構允許快速鍵查找,第二個結構負責排序。 對於更大的表,鏈接列表無法放入單個頁面,因此它是鏈接頁面的列表,其中每個頁面的條目保持順序,在索引創建期間指定。
但是,認為這些頁面一起位於磁盤上的想法是錯誤的。 事實上,它們更有可能分布在不同的位置。 為了根據索引的順序讀取頁面,系統必須執行隨機磁盤讀取。 與順序訪問相比,隨機磁盤 IO 是昂貴的。 因此,好的優化器會更喜歡SeqScan
。
我強烈推薦“SQL Performance Explained”一書以更好地理解索引。 它也可以在線獲得。
到底是怎么回事?
您的OFFSET
子句會導致數據庫讀取索引的鍵鏈接列表(導致大量隨機磁盤讀取),而不是丟棄所有這些結果,直到您達到想要的偏移量。 事實上,Postgres 決定在這里使用SeqScan
+ Sort
是件好事——這應該會更快。
您可以通過以下方式檢查此假設:
OFFSET
查詢的EXPLAIN (analyze, buffers)
SET enable_seqscan TO 'off';
EXPLAIN (analyze, buffers)
,比較結果。 一般來說,最好避免使用OFFSET
,因為 DBMS 在這里並不總是選擇正確的方法。 (順便說一句,您使用的是哪個版本的 PostgreSQL?)下面是它對不同偏移值的執行情況的比較。
編輯:為了避免OFFSET
,必須將分頁基於表中存在的真實數據,並且是索引的一部分。 對於這種特殊情況,可能會出現以下情況:
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.