支援的版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2

11.8. 部分索引 #

部分索引 是在資料表的一個子集上建立的索引;此子集由條件表示式定義(稱為部分索引的述詞)。索引僅包含滿足述詞的那些資料表列的項目。部分索引是一項特殊功能,但在某些情況下它們非常有用。

使用部分索引的一個主要原因是避免索引常見值。由於搜尋常見值(佔資料表列的百分之幾以上)的查詢無論如何都不會使用索引,因此根本沒有必要將這些列保留在索引中。這會減少索引的大小,從而加快那些確實使用索引的查詢。它還將加快許多資料表更新操作,因為索引不需要在所有情況下都進行更新。範例 11.1 顯示了這個想法的一個可能應用。

範例 11.1. 設定部分索引以排除常見值

假設您將網站伺服器存取日誌儲存在資料庫中。大多數存取來自您組織的 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';

這裡查詢的 IP 位址被部分索引涵蓋。以下查詢無法使用部分索引,因為它使用的 IP 位址被排除在索引之外

SELECT *
FROM access_log
WHERE url = '/index.html' AND client_ip = inet '192.168.100.23';

請注意,這種部分索引要求預先確定常見值,因此這種部分索引最適合於資料分佈不變的情況。可以偶爾重新建立此類索引以調整新的資料分佈,但這會增加維護工作。


部分索引的另一種可能用途是從索引中排除典型查詢工作負載不感興趣的值;如 範例 11.2 所示。這會產生與上面列出的相同的優點,但它會阻止透過該索引存取不感興趣的值,即使在這種情況下索引掃描可能是有利可圖的。顯然,為這種情況設定部分索引需要非常小心和實驗。

範例 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所示。這會在滿足索引謂詞的列之間強制唯一性,而不限制不滿足索引謂詞的列。

範例 11.3. 建立部分唯一索引

假設我們有一個描述測試結果的表格。 我們希望確保給定的主體和目標組合只有一個「successful」條目,但可能會有任意數量的「unsuccessful」條目。以下是一種方法:

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

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

當成功的測試很少而失敗的測試很多時,這是一種特別有效的方法。也可以透過使用帶有 IS NULL 限制的唯一部分索引,來允許一個欄位中只有一個 null。


最後,部分索引也可用於覆蓋系統的查詢計畫選擇。此外,具有特殊分佈的資料集可能會導致系統在使用索引時實際上不應該使用。在這種情況下,可以設定索引,使其對於有問題的查詢不可用。通常,PostgreSQL 對於索引的使用做出了合理的選擇(例如,在檢索常見值時避免使用它們,因此前面的範例實際上只節省了索引大小,並非必須避免索引使用),而極不正確的計畫選擇是導致錯誤報告的原因。

請記住,設定部分索引表示您至少和查詢計畫器一樣了解情況,特別是您知道何時索引可能是有益的。形成這些知識需要經驗並理解 PostgreSQL 中的索引是如何運作的。在大多數情況下,部分索引優於常規索引的優勢將是最小的。在某些情況下,它們會產生完全相反的效果,如範例 11.4所示。

範例 11.4. 請勿使用部分索引來代替分割區

您可能會想建立大量不重疊的部分索引,例如

CREATE INDEX mytable_cat_1 ON mytable (data) WHERE category = 1;
CREATE INDEX mytable_cat_2 ON mytable (data) WHERE category = 2;
CREATE INDEX mytable_cat_3 ON mytable (data) WHERE category = 3;
...
CREATE INDEX mytable_cat_N ON mytable (data) WHERE category = N;

這是一個壞主意!幾乎可以肯定的是,使用單個非部分索引會更好,如下宣告:

CREATE INDEX mytable_cat_data ON mytable (category, data);

(為了 第 11.3 節 中描述的原因,請將 category 欄位放在首位。)雖然在這個較大的索引中進行搜尋可能需要比在較小索引中進行搜尋下降更多層樹狀結構,但幾乎可以肯定這比選擇適當的部分索引所需的計畫器工作量要便宜。 問題的核心在於系統不了解部分索引之間的關係,並且會費力地測試每個索引以查看它是否適用於當前查詢。

如果您的表格足夠大,以至於單個索引確實是個壞主意,則應考慮使用分割區(請參閱第 5.12 節)。使用該機制,系統確實了解表格和索引是不重疊的,因此可以實現更好的效能。


有關部分索引的更多資訊,請參閱 [ston89b][olson93][seshadri95]

提交更正

如果您在文件中發現任何不正確、與您使用特定功能不符或需要進一步澄清的地方,請使用此表格回報文件問題。