33.13. 擴展索引接口

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

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

33.13.1. 索引方法和操作符表

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

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

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

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

33.13.2. 索引方法策略

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

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

Table 33-2. B-tree 策略

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

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

Table 33-3. 散列索引

操作策略號
等于1

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

Table 33-4. R-tree 策略

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

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

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

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

33.13.3. 索引方法支持過程

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

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

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

Table 33-5. B-tree 支持函數

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

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

Table 33-6. 散列支持函數

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

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

Table 33-7. R-tree 支持函數

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

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

Table 33-8. GiST 支持函數

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

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

33.13.4. 例子

既然我們已經了解了這些概念,那麼現在就是我們承諾過的創建一個 新的操作符表的例子。 操作符表封裝了那些以絕對值順序對復數排序的操作符,這樣我們 就可以選擇 complex_abs_ops 這個名字。 首先,我們需要一個操作符集合。 用于定義操作符的過程已經在Section 33.11討論過了. 對這個用于 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 就行了。

33.13.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 的時候必須處理數據類型轉換。