PostgreSQL索引介紹


INDEX

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

介紹

假設我們有一個類似這樣的表:

CREATE TABLE test1 (
    id integer,
    content varchar
);

應用程序發出許多類似以下的這種查詢:

SELECT content FROM test1 WHERE id = constant;

沒有提前的准備,系統將不得不逐行掃描整個test1表,以查找所有匹配的條目。如果test1中有很多行,並且這樣的查詢僅僅返回幾行(可能是零或一行),這顯然是一種低效的方法。但是如果系統已被指示在id列上維護索引,則可以使用更有效的方法來定位匹配的行。例如,它可能只需要在搜索樹中深入幾層。

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

可以使用下邊的命令可以在id列上創建一個索引:

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開發人員來檢查問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM