31.14. 擴展索引接口

到目前為止我們描述的過程可以讓您定義一個新類型,新函數和新操作符. 但是,我們還不能在一個新資料類型的字串上面定義一個索引。為了做這 些事情,我們必須為新資料類型定義一個 操作符資料表。 我們講在一個真實的例子的環境中來描述操作符資料表: 一個用於 B-tree 訪問方法的新的操作符資料表,它保存複數並對之以絕對值 遞增的順序排序。

注意: PostgreSQL 版本7.3之前,我們必須 手工給系統資料表 pg_amoppg_amproc,和 pg_opclass 添加記錄,以便於建立用戶定義操作符資料表。 現在這個方法已經廢棄了,因為我們有了 CREATE OPERATOR CLASS, 它在建立必要的資料表記錄的時候更簡單並且更不容易出錯。

31.14.1. 索引方法和操作符資料表

pg_am 資料表為每個索引方法(內部稱作訪問方法)都包含一條記錄. 對資料表的普通訪問方法的支援內建於 PostgreSQL,但所有訪問方法在 pg_am 裡都有描述. 我們可以透過定義要求的接口過程並在 pg_am 裡建立一個行的辦法增加一個索引訪問方法 — 但那些遠遠超出了本章 的內容。

一個索引方法的過程並不直接知道任何該索引方法將要操作的 資料類型的訊息。而是操作符資料表資料表明索引方法在操作特定 資料類型的時候需要使用的操作集合。操作符資料表的名稱由來是因為它們 聲明的一件事情是一種索引可以使用的 WHERE 子句的操作符集(也就是說 可以轉化成一個索引掃瞄條件)。一個操作符資料表也可以聲明一些索引 方法需要的內部操作的支援過程,但是它們並不直接和可以 與索引一起使用的 WHERE 子句操作符相關。

我們可以為同一個資料類型和索引方法定義多個操作符資料表。 這麼做的結果是,我們可以為一種資料類型定義多套索引語義。比如, 一個 B-tree 索引要求為它操作的每種資料類型定義一個排序順序。 對於一個複數資料類型而言,有一個透過複數絕對值對資料排序的 B-tree 操作符資料表可能會有用,還有一個是用實部排序,等等。 通常其中一個操作符資料表會被認為最常用的,並且被標記為該資料類型和索引 方法的預設操作符資料表。

同樣的操作符資料表名字可以用於多種不同的索引方法(比如,B-tree 和散列訪問 方法都有叫 oid_ops 的操作符資料表),但是每個這樣的 資料表都是一個獨立的實體,必須分別定義。

31.14.2. 索引方法策略

和一種操作符資料表相關聯的操作符是透過"策略號"標識的, 策略號用於標識每種操作符在它的操作符資料表環境裡的語義。 比如,B-tree 對鍵字有嚴格的排序要求,小於到大於,因此,像"小於""大於或等於"這樣的操作符都是 B-tree 所感興趣的。 因為 PostgreSQL 允許用戶定義操作符, PostgreSQL 無法查看操作符的名字 (比如,<>=)就明白它進行的比較是什麼。 實際上,索引索引方法定義了一套"策略",它可以看作時一般性的操作符。 每種操作符資料表顯示對於特定資料類型,是哪種實際操作符對應每種策略, 以及解釋索引的語義。

B-tree 索引定義了五種策略.在 Table 31-2 中顯示。

Table 31-2. B-tree 策略

操作索引
小於1
小於或等於2
等於3
大於或等於4
大於5

散列索引只資料表示按位的相等,因此它們只定義了一個策略, 在 Table 31-3 裡顯示。

Table 31-3. 散列索引

操作策略號
等於1

R-tree 索引資料表達方形包含關係。它們定義了八個策略, 在 Table 31-4 裡顯示。

Table 31-4. R-tree 策略

操作策略號
在...左邊1
在...左邊或者重疊2
重疊3
在...右邊或者重疊4
在...右邊5
相同6
包含7
被包含8

GiST 索引甚至更加靈活:它們根本就沒有固定地策略集。實際上,是 每個特定 GiST 操作符資料表的"一致性"支援過程解釋策略號是什麼樣子。

請注意,所有策略操作符都返回布爾值。實際上,所有定義為索引方法 策略的操作符都必須返回類型boolean,因為它們必須出現在 一個 WHERE 子句的頂層,這樣才能被一個索引使用。

順便提一下,pg_am 裡的 amorderstrategy 字串告訴我們該索引方法是否支援排序的掃瞄。零意味著它不支援; 如果它支援,amorderstrategy 是對應該排序操作符 的策略號。比如,B-tree 的 amorderstrategy = 1, 是它的"小於"的策略號。

31.14.3. 索引方法支援過程

有時候,策略的訊息還不足以讓系統決定如何使用某個索引. 實際上,索引方法就需要附加的一些過程來保證能夠工作. 例如,B-tree 索引方法必須能夠比較兩個鍵字以決定其中一個是大於,等於, 還是小於另外一個. 類似的還有 R-tree 索引方法必須能夠計算長方形的相交, 並,和大小等.這些操作和 SQL 命令條件裡使用的操作符並不對應; 它們是被索引方法的管理性質的過程內部調用的過程.

就像策略一樣,操作符資料表聲明在一定的資料類型和語義解釋的條件下, 哪個特定函數對應這些角色中的哪一個。索引方法聲明它需要的函數集, 兒操作符資料表透過給它們賦予"支援函數編號"來標識要使用的正確的函數。

B-tree 需要一個支援函數,在 Table 31-5 裡顯示。

Table 31-5. B-tree 支援函數

函數支援號
比較兩個鍵字並且返回一個小於零,零,或者大於零的整數, 標識第一個鍵字是小於,等於還是大於第二個鍵字。 1

類似的是散列索引也需要一個支援函數,在 Table 31-6 裡顯示。

Table 31-6. 散列支援函數

函數支援號
為一個鍵字比較散列值1

R-tree 索引需要三個支援函數,在 Table 31-7 裡顯示。

Table 31-7. R-tree 支援函數

函數支援號
聯合1
2
大小3

Gist 索引需要七種支援函數,在 Table 31-8 裡顯示。

Table 31-8. GiST 支援函數

函數支援號
一致性1
聯合2
壓縮3
解壓縮4
處罰5
選擇分裂6
等於7

和策略操作符不同,支援函數返回特定索引方法預期的資料類型,比如在 B-tree 的情況下,返回一個符號整數。

31.14.4. 例子

既然我們已經瞭解了這些概念,那麼現在就是我們承諾過的建立一個 新的操作符資料表的例子。 操作符資料表封裝了那些以絕對值順序對複數排序的操作符,這樣我們 就可以選擇 complex_abs_ops 這個名字。 首先,我們需要一個操作符集合。 用於定義操作符的過程已經在Section 31.12討論過了. 對這個用於 B-tree 的 complex_abs_ops 操作符資料表, 我們需要的操作符是:

用於相等操作的 C 代碼看起來像這樣:

#define Mag(c) ((c)->x*(c)->x + (c)->y*(c)->y)

bool
complex_abs_eq(Complex *a, Complex *b)
{
    double amag = Mag(a), bmag = Mag(b);
    return (amag==bmag);
}

其它四個操作符非常類似.您可以在原始碼的 src/tutorial/complex.csrc/tutorial/complex.sql 獲取代碼。

然後基於這些函數聲明函數和操作符:

CREATE FUNCTION complex_abs_eq(complex, complex) RETURNS boolean
    AS 'filename', 'complex_abs_eq'
    LANGUAGE C;

CREATE OPERATOR = (
    leftarg = complex,
    rightarg = complex,
    procedure = complex_abs_eq,
    restrict = eqsel,
    join = eqjoinsel
);

聲明限制和連接選擇性函數是非常重要的,否則優化器將無法有效地 利用索引。請注意,小於,等於和大於的情況下應該使用不同的選擇性 函數。

其它幾個值得一提的問題問題要在這裡出現︰

下一步是註冊 B-tree 需要的比較"支援過程"。 實現這個的例子 C 代碼在包含操作符過程的同一個文件中, 下面是定義函數的方法:

CREATE FUNCTION complex_abs_cmp(complex, complex)
    RETURNS integer
    AS 'filename'
    LANGUAGE C;

既然我們已經有了需要的操作符何支援過程, 我們就可以最後建立這個操作符資料表了:

CREATE OPERATOR CLASS complex_abs_ops
    DEFAULT FOR TYPE complex USING btree AS
        OPERATOR        1       < ,
        OPERATOR        2       <= ,
        OPERATOR        3       = ,
        OPERATOR        4       >= ,
        OPERATOR        5       > ,
        FUNCTION        1       complex_abs_cmp(complex, complex);

這樣我們就完成了!現在我們可以在一個 complex 列上建立和使用 B-tree 索引了.

我們可以把操作符記錄寫得更冗余一些,像

        OPERATOR        1       < (complex, complex) ,

但是如果該操作符接受的資料類型是我們定義的操作符資料表處理的東西, 那就沒必要這麼做。

上面的例子假設您像把這個新操作符資料表作為 complex 資料類型 的預設的 B-tree 操作符資料表。如果您不想這麼做,只要去掉關鍵字 DEFAULT 就行了。

31.14.5. 操作符資料表的特殊特性

還有兩種操作符資料表的特殊特性我們沒有討論,主要是因為它們 對於預設的 B-tree 索引索引方法並不非常有用。

通常,把一個操作符聲明為一個操作符資料表的成員意味著索引索引方法 可以使用該操作符檢索滿足一個 WHERE 條件的行集合。比如,

SELECT * FROM table WHERE integer_column < 4;

可以由一個建立在整數字串上的 B-tree 索引精確地滿足。但是有時候 會有這樣的現象:索引是用作匹配資料行的並不精確的指向。比如, 如果一個 R-tree 索引只為對像儲存周界的方塊,那麼它就無法精確地 滿足一個兩個非方形對像(比如多邊形)之間是否覆蓋的WHERE條件的測試。 但是我們可以使用這個索引找出那些周界方塊和目標對象的周界方塊 重合的對象,然後只在索引找到的對象上做精確的重合測試。如果這種 情形可以透過,那我們就說索引對操作符是"鬆散的", 並且我們在 CREATE OPERATOR CLASS 命令裡給 OPERATOR 子句增加 RECHECK。如果索引保證 返回所有要求的資料加上一些附加的行,那麼 RECHECK 就合法, 這些額外的資料就可以透過執行最初的操作符調用消除。

再考慮我們再索引中只儲存複雜對像(比如多邊形)的周界方塊的情形。 這種情況下我們在索引條目裡儲存整個多邊形沒有太多的數值 — 我們 也可以只儲存更簡單的類型 box 的對象。 這種情形由 CREATE OPERATOR CLASS 裡的 STORAGE 選項儲存:我們可以寫類似這樣的東西

CREATE OPERATOR CLASS polygon_ops
    DEFAULT FOR TYPE polygon USING gist AS
        ...
        STORAGE box;

目前,只有 GiST 索引方法支援與字串資料類型不同的 STORAGE 類型。 GiST compressdecompress 支援過程 在使用 STORAGE 的時候必須處理資料類型轉換。