PostgreSQL索引介紹

INDEX

索引是加強數據庫性能的經常使用方法。索引使得數據庫在查找和檢索數據庫的特定行的時候比沒有索引快的多。但索引也增長了整個數據庫系統的開銷,因此應該合理使用。html

介紹

假設咱們有一個相似這樣的表:程序員

CREATE TABLE test1 (
    id integer,
    content varchar
);

應用程序發出許多相似如下的這種查詢:web

SELECT content FROM test1 WHERE id = constant;

沒有提早的準備,系統將不得不逐行掃描整個test1表,以查找全部匹配的條目。若是test1中有不少行,而且這樣的查詢僅僅返回幾行(多是零或一行),這顯然是一種低效的方法。可是若是系統已被指示在id列上維護索引,則可使用更有效的方法來定位匹配的行。例如,它可能只須要在搜索樹中深刻幾層。正則表達式

大多數非小說類書中都使用相似的方法:讀者常常查閱的術語和概念在本書末尾以字母索引的形式收集。感興趣的讀者能夠相對快速地掃描索引並翻轉到適當的頁面,而沒必要閱讀整本書以找到感興趣的材料。正如做者的任務是預測讀者可能會查找的項目,數據庫程序員的任務是預見哪些索引將是有用的。算法

可使用下邊的命令能夠在id列上建立一個索引:sql

CREATE INDEX test1_id_index ON test1 (id);

名稱test1_id_index能夠自由選擇,但您應該選擇一些可讓您記住索引的內容的名稱。數據庫

要刪除索引,請使用DROP INDEX命令。索引能夠隨時添加到表中並從表中刪除。數組

一旦建立了索引,就不須要進一步的干預:當表被修改時,系統將更新索引,當planner認爲使用索引比順序的表掃描更有效的時候,它會使用索引。可是,您可能須要按期運行ANALYZE命令來更新統計信息,以容許查詢計劃者作出有根據的決策。有關如何肯定索引是否被使用以及計劃者什麼時候以及爲何選擇不使用索引的信息,請參閱第14章緩存

具備搜索條件的UPDATE和DELETE命令的查詢條件也可使用索引來優化。索引也能夠用於鏈接搜索。所以,在做爲鏈接條件一部分的列上定義的索引也能夠顯著加快使用鏈接的查詢。服務器

在大型表上建立索引可能須要很長時間。默認狀況下,PostgreSQL容許讀表(SELECT語句)與索引建立並行發生,但寫入(INSERT,UPDATE,DELETE)將被阻止,直到索引生成完成。在生產環境中,這一般是不能接受的。能夠設置容許寫入與索引建立並行發生,但有幾個須要注意的注意事項 - 有關更多信息,請參閱併發構建索引

建立索引後,系統必須保持索引與數據表的同步。這增長了數據操做操做的開銷。所以,查詢中不多或從未使用的索引應該被刪除。

索引類型

PostgreSQL提供了幾種索引類型:B-tree,Hash,GiST,SP-GiST,GIN和BRIN。每一個索引類型使用不一樣的算法,適合不一樣種類的查詢。默認狀況下,CREATE INDEX命令建立B-tree索引,這符合最多見的狀況。

B-tree能夠處理對能夠排序成某些順序的數據的等式和範圍查詢。特別地,當索引列參與使用如下運算符之一的比較時, PostgreSQL查詢計劃器將考慮使用B-tree索引:

<
<=
=
>=
>

也可使用B-tree索引搜索來實現與這些運算符的組合相同的構造,如BETWEEN和IN。此外,索引列上的IS NULL或IS NOT NULL條件能夠與B-tree索引一塊兒使用。

對於涉及模式匹配運算符LIKE的查詢,優化器還可使用B-tree索引,若是模式是常量,而且錨定到字符串的開頭,例如col LIKE 'foo%'或 col〜'^ foo',但不能是col LIKE'%bar'。可是,若是您的數據庫不使用C語言環境,則須要使用特殊的運算符類建立索引,以支持對模式匹配查詢的索引;見下文第11.9節。也能夠對 ILIKE和〜*使用B-tree索引,但只有當模式以非字母字符(即不受大小寫轉換影響的字符)開始時才能夠。

B-tree索引也可用於按排序順序檢索數據。這並不老是比簡單的掃描和排序更快,但它一般是有益的。

Hash索引只能處理簡單的等式比較。當使用=運算符進行比較時,查詢計劃器將考慮使用Hash索引。如下命令用於建立Hash索引:

CREATE INDEX name ON table USING HASH (column);

Hash索引操做目前不記錄WAL-log,因此若是有沒有寫入的更改,Hash索引可能須要在數據庫崩潰後用REINDEX重建。此外,在初始基本備份以後,不會經過流式或基於文件的複製來複制Hash索引的更改,所以它們對隨後使用它們的查詢給出錯誤的答案。因爲這些緣由,目前不鼓勵使用Hash索引。

GiST索引不是一種單一的索引,而是能夠實現許多不一樣索引策略的基礎設施。所以,可使用GiST索引的特定運算符根據索引策略(運算符類)而變化。例如,PostgreSQL的標準發佈版包括幾個二維幾何數據類型的GiST運算符類,它們支持使用這些運算符的索引查詢:

<<
&<
&>
>>
<<|
&<|
|&>
|>>
@>
<@
~=
&&

(有關這些操做符的含義,請參見第9.11節。)標準發佈版中包含的GiST操做員類別見表61-1。許多其餘GiST操做員類別能夠在contrib集合中或單獨的項目中使用。有關更多信息,請參閱第61章

GiST索引還可以優化「nearest-neighbor」搜索,例如:

SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;

它找到最接近給定目標點的十個位置。這樣作的能力又取決於所使用的特定操做符類。在表61-1中,能夠以這種方式使用的操做員列在「Ordering Operators」一欄中。

SP-GiST索引(相似GiST索引)提供支持各類搜索的基礎架構。 SP-GiST容許實現各類不一樣的不平衡的基於磁盤的數據結構,例如四叉樹,k-d樹和基數樹(嘗試)。例如,PostgreSQL的標準發佈版包括用於二維點的SP-GiST運算符類,它們支持使用這些運算符的索引查詢:

<<
>>
~=
<@
<^
>^

(有關這些操做符的含義,請參見9.11節。)標準配置中包含的SP-GiST操做員類別見表62-1。有關更多信息,請參見第62章

GIN索引是適用於包含多個組件值的數據值(如數組)的「反向索引」。反向索引包含每一個組件值的單獨條目,而且能夠有效地處理測試特定組件值的存在的查詢。

像GiST和SP-GiST同樣,GIN能夠支持許多不一樣的用戶定義的索引策略,而且可使用GIN索引的特定運算符根據索引策略而變化。例如,PostgreSQL的標準發佈版包括一維數組的GIN運算符類,它們支持使用這些運算符的索引查詢:

<@
@>
=
&&

(有關這些操做符的含義,請參見第9.18節。)標準分配中包含的GIN運算符類別見表63-1。許多其餘GIN操做員類能夠在contrib集合中或單獨的項目中使用。更多信息,請參見第63章

BRIN索引(Block Range INdexes的縮寫)存儲關於存儲在表的連續物理塊範圍內的值的摘要。像GiST,SP-GiST和GIN同樣,BRIN能夠支持許多不一樣的索引策略,而且可使用BRIN索引的特定操做符根據索引策略而變化。對於具備線性排序順序的數據類型,索引數據對應於每一個塊範圍的列中值的最小值和最大值。這支持使用這些運算符的索引查詢:

<
<=
=
>=

表64-1列出了標準配置中包含的BRIN運算符類。有關更多信息,請參見第64章

多列索引

能夠在表的多個列上定義索引。例如,若是您有一張相似的數據表:

CREATE TABLE test2 (
  major int,
  minor int,
  name varchar
);

(好比,你的/ dev目錄保存在數據庫中...),你常常發出以下的查詢:

SELECT name FROM test2 WHERE major = constant AND minor = constant;

那麼在major和minor列上定義一個索引多是合適的:

CREATE INDEX test2_mm_idx ON test2 (major, minor);

目前,只有B-tree,GiST,GIN和BRIN索引類型支持多列索引。最多能夠指定32列。 (構建PostgreSQL時能夠更改此限制;請參閱pg_config_manual.h文件。)

多列B樹索引能夠用於涉及索引列的任何子集的查詢條件,可是當前導(最左側)列存在約束時,索引效率最高。確切的規則是,前導列上的等式約束以及不具備相等約束的第一列上的任何不等式約束將用於限制掃描的索引部分。在索引中檢查這些列右側的列的約束,所以它們保存對錶的訪問,但它們不會減小必須掃描的索引部分。例如,給定(a,b,c)上的索引和查詢條件WHERE a = 5 AND b> = 42 AND c <77,必須從a = 5和b = 42經過最後一個條目,a = 5,c> = 77的索引條目將被跳過,但仍需掃描。這個索引原則上能夠用於對b和/或c有約束的查詢,而對b沒有約束,可是整個索引必須被掃描,因此在大多數狀況下,計劃員會喜歡使用索引進行順序表掃描。

多列GiST索引可用於涉及索引列的任何子集的查詢條件。附加列上的條件限制索引返回的條目,但第一列中的條件是肯定須要掃描多少索引的最重要的條件。若是GiST索引的第一列只有幾個不一樣的值,即便附加列中有不少不一樣的值,GiST索引將相對無效。

多列GIN索引可用於涉及索引列的任何子集的查詢條件。與B-tree或GiST不一樣,索引搜索有效性是相同的,不管查詢條件使用哪一個索引列。

多列BRIN索引可用於涉及索引列的任何子集的查詢條件。像GIN同樣,與B-tree或GiST不一樣,索引搜索的有效性是同樣的,不管查詢條件使用哪一個索引列。在單個表上具備多個BRIN索引而不是一個多列BRIN索引的惟一緣由是具備不一樣的pages_per_range存儲參數。

固然,每列必須與適用於索引類型的操做符一塊兒使用;涉及其餘操做符的狀況將不會考慮使用索引。

多列索引應謹慎使用。在大多數狀況下,單列上的索引就足以節省空間和時間。具備三列以上的索引不太多是有用的,除非表的使用很是風格化。有關不一樣索引配置的優勢的一些討論,請參見第11.5節第11.11節

索引和ORDER BY

除了簡單地查找要由查詢返回的行以外,索引可能可以以特定的排序順序傳遞它們。這容許在沒有單獨的排序步驟的狀況下履行查詢的ORDER BY規範。在PostgreSQL當前支持的索引類型中,只有B-tree能夠產生排序的輸出 - 其餘索引類型以未指定的實現依賴順序返回匹配的行。

Planner將經過掃描與規範相匹配的可用索引,或經過以物理順序掃描表並進行明確排序來考慮知足ORDER BY規範。對於須要掃描表的大部分的查詢,顯式排序可能比使用索引更快,由於遵循順序訪問模式須要更少的磁盤I / O。當只須要讀取幾行時,索引更有用。一個重要的特殊狀況是ORDER BY與LIMIT n組合:顯式排序將必須處理全部數據以識別前n行,但若是存在與ORDER BY匹配的索引,則能夠直接檢索前n行,而不掃描其他部分。

默認狀況下,B-tree索引按照升序存儲其條目,最後爲null。這意味着對列x上的索引的正向掃描產生知足ORDER BY x(或更詳細地,ORDER BY x ASC NULLS LAST)的輸出。索引也能夠向後掃描,產生知足ORDER BY x DESC的輸出(或更詳細地,ORDER BY x DESC NULLS FIRST,由於NULLS FIRST是ORDER BY DESC的默認值)。

您能夠經過在建立索引時包含ASC,DESC,NULLS FIRST和/或NULLS LAST選項來調整B樹索引的排序;例如:

CREATE INDEX test2_info_nulls_low ON test2 (info NULLS FIRST);
CREATE INDEX test3_desc_index ON test3 (id DESC NULLS LAST);

以null first存儲的索引首先能夠知足ORDER BY x ASC NULLS FIRST或ORDER BY x DESC NULLS LAST,具體取決於掃描的方向。

您可能會想,爲何要提供全部四個選項,當兩個選項以及反向掃描的可能性將涵蓋ORDER BY的全部變體。在單列索引中,這些選項確實是多餘的,可是在多列索引中它們可能頗有用。考慮(x,y)上的兩列索引:若是咱們向前掃描,則能夠知足ORDER BY x,y,或者若是咱們向後掃描,則能夠知足ORDER BY x DESC,y DESC。可是可能應用程序常常須要使用ORDER BY x ASC,y DESC。沒有辦法從一個簡單的索引中得到這個排序,但若是索引被定義爲(x ASC,y DESC)或(x DESC,y ASC),這是可能的。

顯然,具備非默認排序順序的索引是一個至關專門的功能,但有時它們可​​覺得某些查詢產生巨大的加速。是否值得維護這樣的索引取決於使用須要特殊排序順序的查詢的頻率。

組合多個索引

單個索引掃描只能使用使用索引列的查詢子句和運算符類的運算符,而且與AND結合。例如,給定(a,b)上的索引,如WHERE a = 5 AND b = 6的查詢條件可使用索引,可是像WHERE a = 5 OR b = 6這樣的查詢沒法直接使用索引。

幸運的是,PostgreSQL可以組合多個索引(包括相同索引的多個使用)來處理單次索引掃描沒法實現的狀況。系統能夠跨多個索引掃描造成AND和OR條件。例如,像WHERE x = 42 OR x = 47 OR x = 53 OR x = 99這樣的查詢能夠分解爲x上的索引的四個獨立掃描,每次使用查詢子句之一進行掃描。而後將這些掃描的結果OR化在一塊兒以產生結果。另外一個例子是,若是咱們在x和y上有單獨的索引,那麼WHERE x = 5 AND y = 6之類的查詢的一個可能的實現是將每一個索引與適當的查詢子句一塊兒使用,而後將索引結果AND結合起來,以識別結果行。

要組合多個索引,系統將掃描每一個所需的索引,並準備內存中的位圖,爲匹配該索引條件的錶行的位置提供報告。而後根據查詢的須要將位圖進行AND和OR操做。最後,訪問並返回實際的錶行。按照物理順序訪問錶行,由於這是位圖的佈局方式;這意味着原始索引的任何排序都將丟失,所以若是查詢具備ORDER BY子句,則將須要單獨的排序步驟。由於這個緣由,而且由於每一個額外的索引掃描增長額外的時間,Planner有時會選擇使用簡單的索引掃描,即便可用的附加索引也可使用。

除了最簡單的應用程序以外,索引的各類組合多是有用的,數據庫開發人員必須進行權衡來決定要提供哪些索引。有時,多列索引是最好的,但有時最好建立單獨的索引並依賴索引組合功能。例如,若是您的工做負載包含有時僅涉及列x的混合查詢,則有時僅列Y列,有時兩列,您能夠選擇在x和y上建立兩個單獨的索引,依賴於索引組合來處理查詢使用兩列。您還能夠在(x,y)上建立多列索引。該索引一般比涉及兩列的查詢的索引組合更有效,但如第11.3節所述,對於僅涉及y的查詢幾乎無效,所以不該該是惟一的索引。多列索引和y上的單獨索引的組合能夠很好地起做用。對於僅涉及x的查詢,可使用多列索引,儘管它可能會較大,所以比x上的索引更慢。最後一個選擇是建立全部三個索引,可是若是表的搜索比更新更頻繁,而且全部三種類型的查詢都是常見的,那麼這多是合理的。若是其中一種查詢類型比其餘類型少得多,那麼您可能會創建只建立最符合常見類型的兩個索引。

惟一索引

索引也可用於強制列的值的惟一性,或多個列的組合值的惟一性。

CREATE UNIQUE INDEX name ON table (column [, ...]);

目前,只有B-tree索引能夠被聲明爲惟一的。

當索引聲明爲惟一時,不容許具備相等索引值的多個錶行。空值不被認爲是相等的。多列惟一索引將僅拒絕全部索引列在多行中相等的狀況。

當爲表定義惟一的約束或主鍵時,PostgreSQL會自動建立惟一的索引。該索引涵蓋構成主鍵或惟一約束的列(若是適用,則爲多列索引),而且是強制約束的機制。

注意:不須要在惟一列上手動建立索引;這樣作只會重複自動建立的索引。

表達式上的索引

索引列沒必要僅僅是基礎表的列,也能夠是從表的一個或多個列計算的函數或標量表達式。此功能對於根據計算結果快速訪問表是很是有用的。

例如,進行區分大小寫比較的常見方法是使用lower()函數:

SELECT * FROM test1 WHERE lower(col1) = 'value';

若是在lower(col1)函數的結果中定義了一個索引,則該查詢可使用索引:

CREATE INDEX test1_lower_col1_idx ON test1 (lower(col1));

若是咱們要聲明這個索引UNIQUE,它將阻止建立其col1值的小寫相同的行以及col1值實際上相同的行。所以,表達式上的索引能夠用於強制不能被定義爲簡單的惟一約束的約束。

另外一個例子,若是一我的常常進行以下查詢:

SELECT * FROM people WHERE (first_name || ' ' || last_name) = 'John Smith';

那麼,能夠建立一個這樣的索引:

CREATE INDEX people_names ON people ((first_name || ' ' || last_name));

CREATE INDEX命令的語法一般須要在索引表達式周圍寫圓括號,如第二個示例所示。 當表達式只是一個函數調用時,能夠省略括號,如第一個示例所示。

索引表達式維護相對昂貴,由於必須爲插入時的每一行計算導出的表達式,而且每當更新它時。 可是,索引表達式在索引搜索期間不從新計算,由於它們已經存儲在索引中。 在上述兩個示例中,系統將查詢視爲WHERE indexedcolumn =「constant」,所以搜索速度等同於任何其餘簡單的索引查詢。 所以,當檢索速度比插入和更新速度更重要時,表達式索引是有用的。

部分索引

部分索引是在表的子集上構建的索引; 該子集由條件表達式(稱爲部分索引的謂詞)定義。 該索引僅包含知足謂詞的那些錶行的條目。 部分索引是一個專門的功能,但有幾種狀況使用部分索引是很是有用的

使用部分索引的一個主要緣由是避免索引常見值。 因爲搜索公共值(一個佔全部錶行的百分之幾)的查詢將不會使用索引,因此根本沒有必要在索引中保留這些行。 這減小了索引的大小,這將加快使用索引的查詢。 它也將加快許多表更新操做,由於索引在全部狀況下都不須要更新。 例11-1顯示了這一想法的可能應用。

Example 11-1。 設置部分索引以排除常見值

假設您將Web服務器訪問日誌存儲在數據庫中。 大多數訪問源自您組織的IP地址範圍,但有些來自其餘地方(例如撥號鏈接上的員工)。 若是您的IP搜索主要用於外部訪問,則可能不須要對與組織的子網對應的IP範圍進行索引。

假設有像這樣的一張表:

CREATE TABLE access_log (
    url varchar,
    client_ip inet,
    ...
);

使用下面的命令來創建一個符合咱們狀況的部分索引

CREATE INDEX access_log_client_ip_ix ON access_log (client_ip)
WHERE NOT (client_ip > inet '192.168.100.0' AND
           client_ip < inet '192.168.100.255');

一個典型的使用這個索引的查詢多是這樣的:
SELECT *
FROM access_log
WHERE url = '/index.html' AND client_ip = inet '212.78.10.32';

下邊的這個查詢不能用到這個索引:
SELECT *
FROM access_log
WHERE client_ip = inet '192.168.100.23';

很明顯這種部分索引要求公共值是預先肯定的,因此這種部分索引最好用於不改變的數據分佈。 能夠偶爾從新建立索引以調整新的數據分佈,但這增長了維護工做。

部分索引的另外一個可能用途是從典型查詢工做負載不感興趣的索引中排除值; 這在例11-2中展現。 這致使與上述相同的優勢,可是它防止經過該索引訪問「不感興趣」的值,即便索引掃描在這種狀況下多是有利的。 顯然,爲這種場景設置部分索引須要大量的關注和試驗。

Example 11-2。 設置部分索引以排除不感興趣的值

若是您有一個包含已計費和未計費訂單的表,其中未計費訂單佔總表的一小部分,而那些是最常訪問的行,則能夠經過僅在未計費的行上建立索引來提升性能。 建立索引的命令以下所示:

CREATE INDEX orders_unbilled_index ON orders (order_nr)
    WHERE billed is not true;

一個用到這個索引的可能的查詢是:

SELECT * FROM orders WHERE billed is not true AND order_nr < 10000;

然而,在哪些不包含order_nr的查詢上也可使用到這個索引:

SELECT * FROM orders WHERE billed is not true AND amount > 5000.00;

因爲系統必須掃描整個索引,所以這不會比amount列上的局部索引更高效。 然而,若是有相對較少的未結算訂單,使用這個部分索引只是找到未結算的訂單多是比較好的。

注意,下邊的查詢用不到這一索引:

SELECT * FROM orders WHERE order_nr = 3501;

3501對應的訂單多是已結算的,也多是未結算的。

示例11-2還說明了索引列和謂詞中使用的列不須要匹配。 PostgreSQL支持具備任意謂詞的部分索引,只要包含索引的列。可是,謂詞必須匹配在索引中受益的查詢中使用的條件。準確地說,只有系統能夠識別查詢的WHERE條件在數學上意味着索引的謂詞時,才能在查詢中使用部分索引。 PostgreSQL沒有一個複雜的定理證實器,能夠識別以不一樣形式編寫的數學等效表達式。 (這樣一個通常的定理證實者不但難以產生,因此實際使用太慢)。系統能夠識別簡單的不平等含義,例如「x <1」表示「x <2」;不然謂詞條件必須與查詢的WHERE條件徹底匹配,不然索引將不被識別爲可用。匹配發生在查詢計劃期間,而不是運行時。所以,參數化查詢子句不適用於部分索引。例如,具備參數的準備好的查詢可能指定「x <?」對於參數的全部可能值,這不會暗示「x <2」。

部分索引的第三個可能用途不須要在查詢中使用索引。 這裏的想法是在表的子集上建立惟一的索引,如例11-3所示。 這會在知足索引謂詞的行中強制執行惟一性,而不會限制那些不符合索引謂詞的行。

Example 11-3. 設置一個部分惟一索引

假設咱們有一個描述測試結果的表格。 咱們但願確保給定的主題和目標組合只有一個「成功」條目,但可能會有任何數量的「不成功」條目。 這裏有一種方法:

CREATE TABLE tests (
    subject text,
    target text,
    success boolean,
    ...
);

CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target)
    WHERE success;

當有不多的成功測試和許多不成功的測試時,這是一種特別有效的方法

最後,部分索引還能夠用來覆蓋系統的查詢計劃選擇。另外,具備特殊分佈的數據集可能致使系統在不該該使用索引時使用索引。在這種狀況下,能夠設置索引,使其不適用於有問題的查詢。一般,PostgreSQL對索引的使用合理的選擇(例如,避免他們在檢索經常使用值,因此早期的例子真的節省了索引的大小,它不須要避免索引的使用),錯誤的計劃是致使錯誤報告的緣由

請記住,設置一個分部索引表示您至少知道Planner所知道的狀況,特別是知道索引什麼時候多是有利的。造成這種知識須要經驗和理解Postgresql的索引如何工做。在大多數狀況下,部分索引相對於常規索引的優點是比較小的。

更多的關於部分索引的信息能夠在這裏找到:The case for partial indexes , Partial indexing in POSTGRES: research project, 和 Generalized Partial Indexes (cached version) .

操做員類和操做員組

索引定義能夠爲索引的每一個列指定一個運算符類。

CREATE INDEX name ON table (column opclass [sort options] [, ...]);

運算符類標識要由該列的索引使用的運算符。例如,類型爲int4的B-tree索引將使用int4_ops類;此運算符類包含類型爲int4的值的比較函數。實際上,列的數據類型的默認操做符類一般就足夠了。使用運算符類的主要緣由是對於某些數據類型,可能有多個有意義的索引行爲。例如,咱們可能但願按絕對值或實數對複數數據類型進行排序。咱們能夠經過爲數據類型定義兩個運算符類,而後在進行索引時選擇適當的類來實現。操做員類肯定基本排序順序(能夠經過添加排序選項COLLATE,ASC / DESC和/或NULLS FIRST / NULLS LAST)進行修改。

除了默認值以外,還有一些內置的運算符類:

運算符類text_pattern_ops,varchar_pattern_ops和bpchar_pattern_ops分別支持類型爲text,varchar和char的B-tree索引。與默認操做符類的區別在於,這些值是嚴格按字符比較而不是根據特定於區域設置的排序規則進行比較的。當數據庫不使用標準「C」語言環境時,這使得這些操做符類適用於涉及模式匹配表達式(LIKE或POSIX正則表達式)的查詢。舉個例子,你可能像這樣索引varchar列:

CREATE INDEX test_index ON test_table (col varchar_pattern_ops);

請注意,若是您但願涉及普通<,<=,> 或 >= 比較的查詢使用索引,您還應該使用默認運算符類建立索引。這樣的查詢不能使用xxx_pattern_ops運算符類。 (可是普通的等式比較可使用這些運算符類。)能夠在不一樣的運算符類的同一列上建立多個索引。若是您使用C語言環境,則不須要xxx_pattern_ops運算符類,由於具備默認運算符類的索引可用於C語言環境中的模式匹配查詢。

如下查詢顯示全部定義的運算符類:

SELECT am.amname AS index_method,
       opc.opcname AS opclass_name,
       opf.opfname AS opfamily_name,
       opc.opcintype::regtype AS indexed_type,
       opc.opcdefault AS is_default
    FROM pg_am am, pg_opclass opc, pg_opfamily opf
    WHERE opc.opcmethod = am.oid AND
          opc.opcfamily = opf.oid
    ORDER BY index_method, opclass_name;

此查詢顯示全部定義的操做員族和每一個系列中包含的全部操做符:

SELECT am.amname AS index_method,
       opf.opfname AS opfamily_name,
       amop.amopopr::regoperator AS opfamily_operator
    FROM pg_am am, pg_opfamily opf, pg_amop amop
    WHERE opf.opfmethod = am.oid AND
          amop.amopfamily = opf.oid
    ORDER BY index_method, opfamily_name, opfamily_operator;

索引和排序規則

索引只能支持每一個索引列的一個排序規則。若是感興趣的是多個排序規則,則可能須要多個索引。

考慮如下的SQL:
CREATE TABLE test1c (
id integer,
content varchar COLLATE "x"
);

CREATE INDEX test1c_content_index ON test1c (content);

索引自動使用底層列的排序規則,因此,對於如下的查詢,

SELECT * FROM test1c WHERE content > constant;

可使用索引,由於默認比較將使用列的排序規則。可是,此索引沒法加速涉及其餘排序規則的查詢。因此若是查詢是如下的形式

SELECT * FROM test1c WHERE content > constant COLLATE "y";

能夠建立一個支持「y」排序規則的附加索引,以下所示:

CREATE INDEX test1c_content_y_index ON test1c (content COLLATE "y");

僅索引掃描(就是所謂的覆蓋索引)

PostgreSQL中的全部索引都是輔助索引,這意味着每一個索引都與表的主數據區域(PostgreSQL術語中稱爲表的堆)分開存儲。這意味着在普通的索引掃描中,每行檢索都須要從索引和堆中獲取數據。此外,雖然與索引條件匹配的索引條目一般在索引中靠近在一塊兒,可是它們引用的錶行可能位於堆中的任何位置。所以,索引掃描的堆訪問部分涉及到堆中的大量隨機訪問,這可能很慢,特別是在傳統的旋轉介質上。 (如第11.5節所述,位圖掃描嘗試經過以排序順序執行堆訪問來減輕此成本,但也僅僅作了這些)

爲了解決這個性能問題,PostgreSQL支持僅索引掃描(就相似其餘數據庫中的覆蓋索引),能夠單獨從索引中響應查詢,而無需任何堆訪問。基本思想是直接從每一個索引條目返回值,而不是查詢關聯的堆條目。這種方法可使用兩個基本限制:

  • 索引類型必須支持僅索引掃描。 B-tree索引老是支持的。 GiST和SP-GiST索引支持一些操做符類的僅索引掃描,而不支持其餘操做符。其餘索引類型不支持。基本要求是索引必須物理存儲或者可以重建每一個索引條目的原始數據值。做爲反例,GIN索引不能支持僅索引掃描,由於每一個索引條目一般只保留原始數據值的一部分。
  • 查詢必須僅引用索引中存儲的列。例如,給定一個也有列z的表的x和y列的索引,這些查詢可使用僅索引掃描:
    SELECT x, y FROM tab WHERE x = 'key';
    SELECT x FROM tab WHERE x = 'key' AND y < 42;
    可是另外的這些不能使用:

    SELECT x, z FROM tab WHERE x = 'key';
    SELECT x FROM tab WHERE x = 'key' AND z < 42;

(表達式索引和部分索引會使此規則複雜化,以下所述)

若是知足這兩個基本要求,那麼查詢所需的全部數據值均可以從索引中得到,所以僅索引掃描在物理上是可行的。但PostgreSQL中的任何表掃描還須要額外的要求:它必須驗證每一個檢索到的行對查詢的MVCC快照是「可見的」,如第13章所述。可見性信息不存儲在索引條目中,僅存儲在堆條目中;因此乍一看,彷佛每行檢索都須要堆訪問。若是錶行最近被修改的話,的確如此。然而,對於不多變化的數據,有一個方法來解決這個問題。 PostgreSQL跟蹤表中堆棧中的每一個頁面,該頁面中存儲的全部行是否足夠舊,以便對當前和將來的全部事務均可見。該信息存儲在表的可見性映射中。僅索引掃描在找到候選索引條目後,檢查相應堆頁的可見性映射位。若是它被設置,該行是已知可見的,所以能夠返回數據,而無需進一步的工做。若是未設置,則必須訪問堆條目以肯定它是否可見,所以在標準索引掃描中不會得到性能優點。即便在成功的狀況下,這種方法交換了對堆訪問的可見性映射訪問;可是因爲可見性映射比它描述的堆小四個數量級,因此須要遠遠少於物理I / O來訪問它。在大多數狀況下,可見性映射仍然始終緩存在內存中。

簡而言之,在給定兩個基本要求的狀況下,僅索引掃描是可行的,只有當表的堆頁的很大一部分設置了它們的全可見映射位時,才能很是有效。可是大部分行不變的表格一般足以使這種類型的掃描在實踐中很是有用。

爲了有效利用僅索引掃描功能,您能夠選擇建立這樣的索引,其中只有前導列旨在匹配WHERE子句,然後續列保存要由查詢返回的「有效負載」數據。例如,若是您一般會運行這樣的查詢:

SELECT y FROM tab WHERE x = 'key';

加速這種查詢的傳統方法將是僅在x上建立索引。可是,(x,y)上的索引將提供將此查詢實現爲僅索引掃描的可能性。如前所述,這樣的索引將更大,所以比單獨的x上的索引更昂貴,所以只有當表已知大部分是靜態時纔是一個比較好的選擇。請注意,索引在(x,y)不是(y,x)上聲明很重要,由於大多數索引類型(的複合索引)(特別是B-tree)的搜索在前導搜索列不匹配的時候效率不高。

原則上,僅索引掃描能夠與表達式索引一塊兒使用。例如,給定f(x)上的索引,其中x是表列,應該能夠執行
SELECT f(x) FROM tab WHERE f(x) < 1;

做爲僅索引掃描;若是f()是一個昂貴的計算函數,這是很是有吸引力的。然而,PostgreSQL的計劃者目​​前對這種狀況作的並非很好。只有查詢所需的全部列均可以從索引中得到時,纔會將查詢視爲可執行的索引掃描。在這個例子中,除了在上下文f(x)中,不須要x,可是規劃者並無注意到,而且認爲只有索引掃描是不可能的。若是僅索引掃描彷佛是足夠有價值的,那麼能夠經過將索引聲明爲on(f(x),x)來解決這個問題,其中第二列不指望在實踐中使用,但只是在說服計劃人員能夠進行僅索引掃描。若是目標是避免從新計算f(x),則另外須要注意的是,規劃者不必定會與索引列中不能在可索引的WHERE子句中使用f(x)。一般會在如上所示的簡單查詢中獲取此權限,但不包括涉及鏈接的查詢。這些缺陷可能會在之後版本的PostgreSQL中獲得補救。

部分索引也與僅索引掃描有趣的交互。考慮示例11-3中所示的部分索引:

CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target)
    WHERE success;

原則上,咱們能夠對此索引進行僅索引掃描,以知足查詢:

SELECT target FROM tests WHERE subject = 'some-subject' AND success;

可是有一個問題:WHERE子句依賴了沒有做爲索引的結果列的success列。儘管如此,僅索引掃描是可能的,由於計劃不須要在運行時從新檢查WHERE子句的那部分:索引中找到的全部條目必須具備success = true,所以不須要在計劃中顯式檢查。 PostgreSQL 9.6及更高版本將會識別這種狀況,並容許生成只針對索引的掃描,但較舊的版本不會生成。

檢查索引的使用狀況

雖然PostgreSQL中的索引不須要維護或調優,但檢查實際查詢工做負載實際使用哪些索引仍然十分重要。使用EXPLAIN命令檢查單個查詢的索引使用狀況。其第14.1節介紹了EXPLAIN的用法。也能夠在運行的服務器中收集有關索引使用狀況的統計信息,如第28.2節所述。

肯定要建立一個什麼類型的索引的過程通常比較難。在上一節中,示例中已經顯示了一些典型的狀況。常常須要進行大量實驗。本節的其他部分提供了一些技巧:

  • 始終先運行ANALYZE。此命令收集表中值的分佈統計信息。須要此信息來估計查詢返回的行數,Planner須要爲每一個可能的查詢計劃分配實際成本。在沒有任何真實的統計數據的狀況下,假定一些默認值,這幾乎是不許確的。所以,檢查應用程序的索引使用而不運行ANALYZE是不可行的。有關詳細信息,請參見第24.1.3節第24.1.6節

  • 使用實際數據進行實驗。使用測試數據設置索引會告訴您測試數據須要哪些索引,但僅僅是對測試數據有效。

    使用很是小的測試數據集尤爲致命。當選擇100000行中的1000個多是索引的候選者時,選擇100行中的1個幾乎不會是這樣的,由於100行可能適合單個磁盤頁面,而且沒有計劃能夠順序地獲取1個磁盤頁面。

    在編寫測試數據時也要當心,當應用程序還沒有投入生產時,這一般是不可避免的。很是類似,徹底隨機或以排序順序插入的值將使統計數據偏離實際數據所具備的分佈。

  • 當不使用索引時,測試可能有助於強制使用它們。運行時參數能夠關閉各類計劃類型(參見第19.7.1節)。例如,關閉最基本的計劃的順序掃描(enable_seqscan)和嵌套循環鏈接(enable_nestloop)將強制系統使用不一樣的計劃。若是系統仍然選擇順序掃描或嵌套循環鏈接,那麼可能還有一個更根本的緣由是爲何索引沒有被使用;例如,查詢條件與索引不匹配。 (什麼樣的查詢可使用上一節中介紹的類型的索引。)

  • 若是強制索引使用確實使用索引,那麼有兩種可能性:系統是正確的,使用索引確實不合適,或者查詢計劃的成本估計並不反映現實狀況。因此你應該檢查使用和不使用索引進行查詢時的時間消耗。 EXPLAIN ANALYZE命令在這裏很是有用。

  • 若是事實證實成本估計是錯誤的,那麼再有兩種可能性。總成本由每一個計劃節點的每行成本乘以計劃節點的選擇性估計值計算。能夠經過運行時參數來調整計劃節點的成本(在第19.7.2節中描述)。選擇性估算不許確是因爲統計數據不足。經過調整統計信息收集參數能夠改善這一點(參見ALTER TABLE)。

若是您沒有成功地把成本調整的更合適,那麼您可能須要明確強制使用索引使用。您還能夠聯繫PostgreSQL開發人員來檢查問題。

相關文章
相關標籤/搜索