PostgreSQL 部分索引

2021-08-27 11:51 更新

一個(gè)部分索引是建立在表的一個(gè)子集上,而該子集則由一個(gè)條件表達(dá)式(被稱為部分索引的謂詞)定義。而索引中只包含那些符合該謂詞的表行的項(xiàng)。部分索引是一種專門的特性,但在很多種情況下它們也很有用。

使用部分索引的一個(gè)主要原因是避免索引公值。由于搜索一個(gè)公值的查詢(一個(gè)在所有表行中占比超過一定百分比的值)不會(huì)使用索引,所以完全沒有理由將這些行保留在索引中。這可以減小索引的尺寸,同時(shí)也將加速使用索引的查詢。它也將加速很多表更新操作,因?yàn)檫@種索引并不需要在所有情況下都被更新。例 11.1展示了一種可能的應(yīng)用:

例 11.1. 建立一個(gè)部分索引來排除公值

假設(shè)我們要在一個(gè)數(shù)據(jù)庫(kù)中保存網(wǎng)頁服務(wù)器訪問日志。大部分訪問都來自于我們組織內(nèi)的IP地址,但是有些來自于其他地方(如使用撥號(hào)連接的員工)。如果我們主要通過IP搜索來自于外部的訪問,我們就沒有必要索引對(duì)應(yīng)于我們組織內(nèi)網(wǎng)的IP范圍。

假設(shè)有這樣一個(gè)表:

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

用以下命令可以創(chuàng)建適用于我們的部分索引:

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');

一個(gè)使用該索引的典型查詢是:

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

此處查詢的IP地址由部分索引覆蓋。以下查詢無法使用部分索引,因?yàn)樗褂脧乃饕信懦?IP 地址:

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

可以看到部分索引查詢要求公值能被預(yù)知,因此部分索引最適合于數(shù)據(jù)分布不會(huì)改變的情況。這樣的索引也可以偶爾被重建來適應(yīng)新的數(shù)據(jù)分布,但是這會(huì)增加維護(hù)負(fù)擔(dān)。


例 11.2展示了部分索引的另一個(gè)可能的用途:從索引中排除那些查詢不感興趣的值。這導(dǎo)致了上述相同的好處,但它防止了通過索引來訪問不感興趣的值,即便在這種情況下一個(gè)索引掃描是有益的。顯然,為這種場(chǎng)景建立部分索引需要很多考慮和實(shí)驗(yàn)。

例 11.2. 建立一個(gè)部分索引來排除不感興趣的值

如果我們有一個(gè)表包含已上賬和未上賬的訂單,其中未上賬的訂單在整個(gè)表中占據(jù)一小部分且它們是最經(jīng)常被訪問的行。我們可以通過只在未上賬的行上創(chuàng)建一個(gè)索引來提高性能。創(chuàng)建索引的命令如下:

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

使用該索引的一個(gè)可能查詢是:

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列上部分索引有效,因?yàn)橄到y(tǒng)必須掃描整個(gè)索引。然而,如果有相對(duì)較少的未上賬訂單,使用這個(gè)部分索引來查找未上賬訂單將會(huì)更好。

注意這個(gè)查詢將不會(huì)使用該索引:

SELECT * FROM orders WHERE order_nr = 3501;

訂單3501可能在已上賬訂單或未上賬訂單中。


例 11.2也顯示索引列和謂詞中使用的列并不需要匹配。PostgreSQL支持使用任意謂詞的部分索引,只要其中涉及的只有被索引表的列。然而,記住謂詞必須匹配在將要受益于索引的查詢中使用的條件。更準(zhǔn)確地,只有當(dāng)系統(tǒng)能識(shí)別查詢的 WHERE條件從數(shù)學(xué)上索引的謂詞時(shí),一個(gè)部分索引才能被用于一個(gè)查詢。PostgreSQL并不能給出一個(gè)精致的定理證明器來識(shí)別寫成不同形式在數(shù)學(xué)上等價(jià)的表達(dá)式(一方面創(chuàng)建這種證明器極端困難,另一方面即便能創(chuàng)建出來對(duì)于實(shí)用也過慢)。系統(tǒng)可以識(shí)別簡(jiǎn)單的不等蘊(yùn)含,例如x < 1蘊(yùn)含x < 2;否則謂詞條件必須準(zhǔn)確匹配查詢的 WHERE條件中的部分,或者索引將不會(huì)被識(shí)別為可用。匹配發(fā)生在查詢規(guī)劃期間而不是運(yùn)行期間。因此,參數(shù)化查詢子句無法配合一個(gè)部分索引工作。例如,對(duì)于參數(shù)的所有可能值來說,一個(gè)具有參數(shù)x < ?的預(yù)備查詢絕不會(huì)蘊(yùn)含x < 2。

部分索引的第三種可能的用途并不要求索引被用于查詢。其思想是在一個(gè)表的子集上創(chuàng)建一個(gè)唯一索引,如例 11.3所示。這對(duì)那些滿足索引謂詞的行強(qiáng)制了唯一性,而對(duì)那些不滿足的行則沒有影響。

例 11.3. 建立一個(gè)部分唯一索引

假設(shè)我們有一個(gè)描述測(cè)試結(jié)果的表。我們希望保證其中對(duì)于一個(gè)給定的主題和目標(biāo)組合只有一個(gè)成功項(xiàng),但其中可能會(huì)有任意多個(gè)不成功項(xiàng)。實(shí)現(xiàn)它的方式是:

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

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

當(dāng)有少數(shù)成功測(cè)試和很多不成功測(cè)試時(shí)這是一種特別有效的方法。通過創(chuàng)建具有IS NULL限制的惟一部分索引,也可以允許列中僅有一個(gè)空。


最后,一個(gè)部分索引也可以被用來重載系統(tǒng)的查詢規(guī)劃選擇。同樣,具有特殊分布的數(shù)據(jù)集可能導(dǎo)致系統(tǒng)在它并不需要索引的時(shí)候選擇使用索引。在此種情況下可以被建立,這樣它將不會(huì)被那些無關(guān)的查詢所用。通常,PostgreSQL會(huì)對(duì)索引使用做出合理的選擇(例如,它會(huì)在檢索公值時(shí)避開索引,這樣前面的例子只能節(jié)約索引尺寸,它并非是避免索引使用所必需的),非常不正確的規(guī)劃選擇則需要作為故障報(bào)告。

記住建立一個(gè)部分索引意味著我們知道的至少和查詢規(guī)劃器所知的一樣多,尤其是我們知道什么時(shí)候一個(gè)索引會(huì)是有益的。 構(gòu)建這些知識(shí)需要經(jīng)驗(yàn)和對(duì)于PostgreSQL中索引工作方式的理解。 在大部分情況下,一個(gè)部分索引相對(duì)于一個(gè)普通索引的優(yōu)勢(shì)很小。在某些情況下,它們會(huì)完全相反,例如例 11.4。

例 11.4. 不要使用部分索引代替分區(qū)

你可能想嘗試創(chuàng)建一組巨大的、不重疊的部分索引,例如

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;

這是個(gè)個(gè)壞主意!幾乎可以肯定,使用一個(gè)非部分索引會(huì)更好一些,聲明如

CREATE INDEX mytable_cat_data ON mytable (category, data);

(將類別列放在前面,基于第 11.3 節(jié)所述的原因。) 雖然在這個(gè)更大的索引中進(jìn)行搜索可能比在更小的索引中進(jìn)行搜索要下降兩倍以上的樹級(jí)別, 但這幾乎肯定會(huì)比選擇適當(dāng)?shù)牟糠炙饕械囊粋€(gè)所需的規(guī)劃器的開銷更便宜。 問題的核心是系統(tǒng)不理解部分索引之間的關(guān)系,并將費(fèi)力地測(cè)試每個(gè)索引,以確定它是否適用于當(dāng)前查詢。

如果你的表足夠大,單個(gè)索引確實(shí)是一個(gè)壞主意,你應(yīng)該考慮使用分區(qū)代替(參見第 5.11 節(jié))。 通過這種機(jī)制,系統(tǒng)理解表和索引是不重疊的,就此而言可以獲得更好的性能。


關(guān)于部分索引的更多信息可以在[ston89b][olson93][seshadri95]中找到。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)