一個部分索引是建立在一張表的一部分上的索引;這部分資料由一個條件表示式(稱為部分索引的謂詞)定義。該索引只包含滿足謂詞的錶行條目。部分索引是一項特殊功能,但在許多情況下都很有用。
使用部分索引的一個主要原因是避免對常用值進行索引。由於查詢常用值(佔表中行數百分比很高的值)時不會使用索引,因此完全沒有必要將這些行保留在索引中。這會減小索引的大小,從而加快那些確實使用了索引的查詢的速度。同時,它還會加快許多表更新操作的速度,因為在所有情況下都不需要更新索引。示例 11.1展示了這種想法的一種可能應用。
示例 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';
此處查詢的 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. 設定部分唯一索引
假設我們有一個描述測試結果的表。我們希望確保對於給定的主題和目標組合只有一個“成功”條目,但可能有任意數量的“不成功”條目。以下是一種實現方法:
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 節所述。)雖然在這個較大的索引中進行搜尋可能比在較小的索引中搜索需要遍歷更多的樹級別,但這幾乎肯定比規劃器選擇合適的部分索引所需的努力要便宜。問題的核心在於系統不理解部分索引之間的關係,並且會費力地測試每一個部分索引以確定其是否適用於當前查詢。
如果您的表足夠大,以至於單個索引確實是個壞主意,那麼您應該考慮使用分割槽(請參閱第 5.12 節)。使用該機制,系統會理解表和索引是不重疊的,因此可以實現更好的效能。
有關部分索引的更多資訊,請參閱[ston89b]、[olson93] 和[seshadri95]。
如果您在文件中發現任何不正確之處、與您對特定功能的實際經驗不符之處或需要進一步闡明之處,請使用此表格報告文件問題。