2010-09-24 53 views
1

我有一個約10k行的普通表,它通常按名爲'name'的列進行排序。所以,我在這個專欄上增加了一個索引。現在選擇它的速度快:使用PostgreSQL中的約束對ORDER BY使用的索引進行索引

EXPLAIN ANALYZE SELECT * FROM crm_venue ORDER BY name ASC LIMIT 10; 
    ...query plan... 
Limit (cost=0.00..1.22 rows=10 width=154) (actual time=0.029..0.065 rows=10 loops=1) 
    -> Index Scan using crm_venue_name on crm_venue (cost=0.00..1317.73 rows=10768  width=154) (actual time=0.026..0.050 rows=10 loops=1) 
Total runtime: 0.130 ms 

如果我增加LIMIT 60(這大概是我在應用程序中使用),總運行時間不太多進一步增加。

因爲我在這張表上使用了「邏輯刪除模式」,所以我只考慮其中的delete_date NULL。因此,這是一種常見的選擇由我自己:

SELECT * FROM crm_venue WHERE delete_date IS NULL ORDER BY name ASC LIMIT 10; 

爲了讓這個查詢瞬間以及我把指數在name列有這樣的約束:

CREATE INDEX name_delete_date_null ON crm_venue (name) WHERE delete_date IS NULL; 

現在是快辦使用邏輯刪除約束進行排序:

EXPLAIN ANALYZE SELECT * FROM crm_venue WHERE delete_date IS NULL ORDER BY name ASC LIMIT 10; 
Limit (cost=0.00..84.93 rows=10 width=154) (actual time=0.020..0.039 rows=10 loops=1) 
    -> Index Scan using name_delete_date_null on crm_venue (cost=0.00..458.62 rows=54 width=154) (actual time=0.018..0.033 rows=10 loops=1) 
Total runtime: 0.076 ms 

太棒了!但這是我讓自己陷入麻煩。應用程序很少調用前10行。所以,讓我們選擇更多的行:

EXPLAIN ANALYZE SELECT * FROM crm_venue WHERE delete_date IS NULL ORDER BY name ASC LIMIT 20; 

Limit (cost=135.81..135.86 rows=20 width=154) (actual time=18.171..18.189 rows=20 loops=1) 
    -> Sort (cost=135.81..135.94 rows=54 width=154) (actual time=18.168..18.173 rows=20 loops=1) 
    Sort Key: name 
    Sort Method: top-N heapsort Memory: 21kB 
    -> Bitmap Heap Scan on crm_venue (cost=4.67..134.37 rows=54 width=154) (actual time=2.355..8.126 rows=10768 loops=1) 
      Recheck Cond: (delete_date IS NULL) 
      -> Bitmap Index Scan on crm_venue_delete_date_null_idx (cost=0.00..4.66 rows=54 width=0) (actual time=2.270..2.270 rows=10768 loops=1) 
       Index Cond: (delete_date IS NULL) 
Total runtime: 18.278 ms 

正如你所看到的,它從0.1毫秒到18!

很明顯會發生什麼是有一個點,其中排序不能再使用索引來運行排序。我注意到,當我將LIMIT數字從20增加到更高的數字時,它總是需要大約20-25毫秒。

我做錯了,還是這是PostgreSQL的限制?爲這種類型的查詢設置索引的最佳方式是什麼?

回答

0

隨着您增加行數,索引基數發生變化。我不確定,但可能是因爲它使用的表中有更多的行,它需要讀取足夠多的表格塊,這些表格塊加上索引塊足以使索引不再有意義使用。這可能是計劃者的錯誤計算。你的名字(被索引的字段)也不是限制索引範圍的字段,這可能會對規劃師數學造成嚴重破壞。

可以嘗試的事情: 在構建統計信息時增加考慮的表的百分比,您的數據可能會出現傾斜,導致統計信息無法獲取真實的代表性示例。

索引所有行,而不僅僅是NULL行,看哪個更好。你甚至可以在NOT NULL的地方嘗試索引。

基於該字段上的索引的羣集可減少所需的數據塊並將其轉換爲範圍掃描。

空值和索引不總是很好。嘗試另一種方式:

alter table crm_venue add column char delete_flag; 
update crm_venue set delete flag='Y' where delete_date is not null; 
update crm_venue set delete flag='N' where delete_date is null; 
create index deleted_venue (delete_flag) where delete_flag = 'N'; 
SELECT * FROM crm_venue WHERE delete__flag='Y' ORDER BY name ASC LIMIT 20; 
1

我的猜測是,因爲在邏輯上,索引是由指向一組數據頁面上的一組行的指針組成的。如果您只抓取一個已知僅具有「已刪除」記錄的頁面,那麼一旦抓取該頁面以僅抓取已刪除的記錄,就不必重新檢查該頁面。

因此,可能是因爲當您執行限制10並按名稱排序時,從索引返回的前10個數據都位於僅包含已刪除記錄的數據頁面(或多個頁面)上。既然它知道這些頁面是同質的,那麼一旦從磁盤中獲取它們,就不必重新檢查它們。一旦您增加到限制20,前20箇中至少有一個在混合頁面上,並且未刪除記錄。這會迫使執行者重新檢查每條記錄,因爲它無法從磁盤或緩存以小於1頁的增量獲取數據頁。

作爲一個實驗,如果您可以創建索引(delete_date,name)併發出命令CLUSTER crm_venue ON,其中索引是您的新索引。這應該按照delete_date和name的排序順序重建表。爲了超級確定,你應該發佈一個REINDEX TABLE crm_venue。現在再次嘗試查詢。由於所有NOT NULL都將聚集在磁盤上,因此可以使用更大的LIMIT值更快地工作。

當然,這一切都是非現成的理論,所以YMMV ...

+0

非常感謝你的答案。不幸的是,使用布爾值而不是日期不是一個選項。將它作爲可空日期是A)比bool更實用,B)現在應用程序承擔這種變化已經太遲了。 – 2010-09-26 17:34:59

+0

我的歉意,我不是故意暗示你需要添加一個布爾值。我很習慣軟刪除「旗幟」的人,我毫不猶豫地寫下了它。我將更新條目以讀取「刪除日期」而不是「delete_flag」,以便更清楚。 – 2010-09-27 14:25:33