B-Tree索引中的最佳排序順序以支持對最近行的查詢?
假設我有一個表,其描述如下:
create table my_table ( id serial, create_date timestamp with time zone default now(), data text );
和類似的查詢:
select * from my_table where create_date >= timestamp with time zone 'yesterday'
理論上哪個索引會更快,為什麼?
create index index_a on my_table (create_date); create index index_b on my_table (create_date DESC);
我不喜歡實際上不是 a而是 a的列的名稱“create_date”。改用**“created_at”**。
date``timestamptz
由於
created_at
can beNULL
,這第 3 個變體將更快(即使不是很多):CREATE INDEX index_c ON my_table (created_at DESC NULLS LAST);
NULL
預設情況下,值在最大值之後排序。DESCENDING
排序順序是完美的反轉,所以NULL
值排在第一位。看:Postgres 可以以幾乎相同的速度向後掃描 B-tree 索引,因此您的兩個變體幾乎是相同的。但是運算符
>=
不包括NULL
值(像大多數運算符一樣)。所以 Postgres 必須先分別跳過前導/尾隨NULL
值。通常不貴,但仍然。**
DESC NULLS LAST
**帶有(or )的索引NULLS FIRST
首先具有最大值,然後具有最大值NULL
(反之亦然),因此查詢可以直接從索引的頂部(底部)開始讀取。如果不能有
NULL
值,就不會有明顯的差異。您應該聲明該列NOT NULL
。(你應該這麼說的。)如果插入帶有嚴格升序的時間戳(並且沒有更新!) - 或者如果對於自“昨天”以來最近插入的行至少是這樣,(相關)行會自動按時間戳進行物理分群。否則,它可以不時對行進行物理集群。(雖然不會干擾數據庫上的並發負載!)這可以產生更大的差異,因為它將必須讀取的數據頁數保持在最低限度。看:
如果您的表很大,則**部分索引**可以支付:
CREATE INDEX index_c_partial ON my_table (created_at DESC NULLS LAST) WHERE created_at >= '2021-06-26 0:0'; -- recent but before yesterday
它切斷了大部分舊行,以便索引縮小到一小部分。
但是由於您的截止 (
'yesterday'
) 是一個移動目標,您必須不時重新創建該索引以刪除舊元組,否則收益會隨著時間的推移而惡化。就像,每天,每週,每月 - 你決定。使用暖記憶體,該部分索引不會比完整索引快多少,但由於它非常小,因此它留在記憶體中的機會相應更大(取決於您的完整設置),這通常會產生很大的不同。(而且它一開始並沒有佔用那麼多資源。)
由於我們現在有這麼小的索引,雖然我們只處理這麼少的列(或者您實際上不需要
SELECT *
開始?!),我們不妨將其設為**覆蓋索引**(Postgres 11 或更高版本):CREATE INDEX index_c_partial_covering ON my_table (created_at DESC NULLS LAST) INCLUDE (id, data) WHERE created_at >= '2021-06-26 0:0';
同樣,細節取決於完整的情況。有關的:
如果滿足某些先決條件,您現在可以獲得更便宜的僅索引掃描。在這種情況下,表中行的物理順序無關緊要。
哦,將該
timestamptz
列移動到表定義中的不同位置。由於對齊填充,您現在擁有它的方式最大化膨脹。列的任何其他位置timestamptz
都更好。像:CREATE TABLE my_table ( , created_at timestamptz DEFAULT now() NOT NULL , id serial NOT NULL PRIMARY KEY , data text );
看: