PostgreSQL中的索引(四) --Btree
Btree的結構
B-tree索引型別,以«btree»訪問方法實現的,適合於可排序的資料。換句話說,必須為資料型別定義«greater»、«greater or equal»、«less»、«less or equal»和«equal»操作符。注意,相同的資料有時可能排序不同,這又回到了操作符家族的概念。
b-樹的索引行被打包到頁中。在葉子頁中,這些行包含要索引的資料(鍵)和對錶行的引用(tid)。在內部頁中,每一行引用索引的一個子頁,幷包含該頁中的最小值。
B樹有一些重要的特徵:
·B-樹是平衡的,即每個葉子頁與根頁之間由相同數量的內部頁分隔。因此,搜尋任何值都需要相同的時間。
·B-樹是多分支的,也就是說,每個頁(通常為8KB)包含很多(數百個)tid。因此,b-樹的深度非常小,對於非常大的表,實際上可以達到4-5。
·索引中的資料按非降序排序(頁之間和每個頁內部都是如此),同級別頁通過雙向列表彼此連線。因此,我們可以通過向一個或另一個方向遍歷列表來獲得有序資料集,而不必每次都返回到根。
索引的第一頁是元資料頁,它引用索引根。內部節點位於根的下面,葉子頁位於最下面一行。向下的箭頭表示葉子節點對錶行(tid)的引用。
等價檢索
讓我們考慮根據條件“indexed-field = expression”在樹中搜索一個值。比如說,我們對49的鍵感興趣。
實際上,一些特殊情況使這個看似簡單的過程變得複雜。例如,索引可以包含非唯一鍵,並且可能有許多相等的值,以至於不能容納在一個頁。回到我們的示例,似乎應該從內部節點的引用向下延伸到值49。但是,從圖中可以清楚地看出,這樣我們將跳過前面頁中的一個«49»鍵。因此,一旦我們在一個內部頁面中找到了一個完全相等的鍵,我們就必須往左下降一個位置,然後從左到右檢視底層的索引行來搜尋所查詢的鍵。
(另一個複雜的問題是,在搜尋過程中,其他程序可以更改資料:可以重新構建樹,可能將頁一分為二,等等。所有的演算法都是為這些併發操作而設計的,在任何可能的情況下都不會相互干擾,也不會導致額外的鎖。但我們將避免對此進行詳述。)
不等價檢索
當按條件“indexed-field≤expression”(或“indexed-field≥expression”)進行搜尋時,首先按相等條件“indexed-field = expression”在索引中找到一個值(如果有),然後按適當的方向遍歷頁頁,直到結束。
«greater»和«less»操作符以類似的方式被支援,除了最初找到的值必須被剔除。
範圍檢索
當按照“expression1≤indexed-field≤expression2”的範圍進行搜尋時,根據條件“indexed-field = expression1”找到一個值,當滿足條件“indexed-field≤expression2”時,繼續遍歷頁;反之亦然:從第二個表達開始,向相反的方向走,直到我們到達第一個表達。
條件23≤n≤64時的過程如圖:
示例
讓我們看一個查詢計劃的示例。與往常一樣,我們使用演示資料庫,這一次我們將考慮aircraft表。它只包含9行,計劃器將選擇不使用索引,因為整個表只在一個頁中。
demo=# select * from aircrafts; aircraft_code | model | range ---------------+---------------------+------- 773 | Boeing 777-300 | 11100 763 | Boeing 767-300 | 7900 SU9 | Sukhoi SuperJet-100 | 3000 320 | Airbus A320-200 | 5700 321 | Airbus A321-200 | 5600 319 | Airbus A319-100 | 6700 733 | Boeing 737-300 | 4200 CN1 | Cessna 208 Caravan | 1200 CR2 | Bombardier CRJ-200 | 2700 (9 rows) demo=# create index on aircrafts(range); demo=# set enable_seqscan = off;
索引建立預設就是btree索引。
使用等價檢索:
demo=# explain(costs off) select * from aircrafts where range = 3000; QUERY PLAN --------------------------------------------------- Index Scan using aircrafts_range_idx on aircrafts Index Cond: (range = 3000) (2 rows)
不等價檢索:
demo=# explain(costs off) select * from aircrafts where range < 3000; QUERY PLAN --------------------------------------------------- Index Scan using aircrafts_range_idx on aircrafts Index Cond: (range < 3000) (2 rows)
根據範圍查詢:
demo=# explain(costs off) select * from aircrafts where range between 3000 and 5000; QUERY PLAN ----------------------------------------------------- Index Scan using aircrafts_range_idx on aircrafts Index Cond: ((range >= 3000) AND (range <= 5000)) (2 rows)
排序
讓我們再次強調一點,對於任何型別的掃描(索引、僅索引或點陣圖),«btree»訪問方法都返回有序的資料,我們可以在上面的圖中清楚地看到這一點。
因此,如果一個表在排序條件下有一個索引,那麼優化器將同時考慮兩種選項:表的索引掃描(它可以隨時返回排序後的資料)和表的順序掃描(隨後對結果進行排序)。
排序順序
在建立索引時,我們可以顯式地指定排序順序。例如,我們可以通過以下方式根據飛行範圍建立索引:
demo=# create index on aircrafts(range desc);
在這種情況下,較大的值將出現在左邊的樹中,較小的值將出現在右邊。如果我們可以在任意方向遍歷索引值,為什麼還需要這樣做呢?
其目的是建立多列索引。讓我們建立一個檢視來顯示飛機模型與傳統的劃分為短,中,和遠端飛機:
demo=# create view aircrafts_v as select model, case when range < 4000 then 1 when range < 10000 then 2 else 3 end as class from aircrafts; demo=# select * from aircrafts_v; model | class ---------------------+------- Boeing 777-300 | 3 Boeing 767-300 | 2 Sukhoi SuperJet-100 | 1 Airbus A320-200 | 2 Airbus A321-200 | 2 Airbus A319-100 | 2 Boeing 737-300 | 2 Cessna 208 Caravan | 1 Bombardier CRJ-200 | 1 (9 rows)
讓我們建立一個索引(使用表示式):
demo=# create index on aircrafts( (case when range < 4000 then 1 when range < 10000 then 2 else 3 end), model);
現在我們可以使用這個索引來獲得資料按兩列升序排序:
demo=# select class, model from aircrafts_v order by class, model; class | model -------+--------------------- 1 | Bombardier CRJ-200 1 | Cessna 208 Caravan 1 | Sukhoi SuperJet-100 2 | Airbus A319-100 2 | Airbus A320-200 2 | Airbus A321-200 2 | Boeing 737-300 2 | Boeing 767-300 3 | Boeing 777-300 (9 rows) demo=# explain(costs off) select class, model from aircrafts_v order by class, model; QUERY PLAN -------------------------------------------------------- Index Scan using aircrafts_case_model_idx on aircrafts (1 row)
同樣的,我們可以執行查詢來對資料進行降序排序:
demo=# select class, model from aircrafts_v order by class desc, model desc; class | model -------+--------------------- 3 | Boeing 777-300 2 | Boeing 767-300 2 | Boeing 737-300 2 | Airbus A321-200 2 | Airbus A320-200 2 | Airbus A319-100 1 | Sukhoi SuperJet-100 1 | Cessna 208 Caravan 1 | Bombardier CRJ-200 (9 rows) demo=# explain(costs off) select class, model from aircrafts_v order by class desc, model desc; QUERY PLAN ----------------------------------------------------------------- Index Scan BACKWARD using aircrafts_case_model_idx on aircrafts (1 row)
但是,我們不能使用這個索引來獲得按一列降序排序、按另一列升序排序的資料。這將需要分別排序:
demo=# explain(costs off) select class, model from aircrafts_v order by class ASC, model DESC; QUERY PLAN ------------------------------------------------- Sort Sort Key: (CASE ... END), aircrafts.model DESC -> Seq Scan on aircrafts (3 rows)
注意,作為最後一種手段,計劃器選擇了順序掃描,而不考慮之前設定的«enable_seqscan = off»。這是因為實際上該設定並沒有禁止表掃描,而只是設定了其cost設定的很大——請檢視帶有«costs on»的計劃。
為了使這個查詢使用索引,後者必須建立所需的排序方向:
demo=# create index aircrafts_case_asc_model_desc_idx on aircrafts( (case when range < 4000 then 1 when range < 10000 then 2 else 3 end) ASC, model DESC); demo=# explain(costs off) select class, model from aircrafts_v order by class ASC, model DESC; QUERY PLAN ----------------------------------------------------------------- Index Scan using aircrafts_case_asc_model_desc_idx on aircrafts (1 row)
列的順序
使用多列索引時出現的另一個問題是索引中列出列的順序。對於B-tree,這個順序非常重要:頁內的資料將按第一個欄位排序,然後按第二個欄位排序,依此類推。
我們可以用符號的方式表示我們在範圍區間和模型上建立的索引:
實際上,這樣一個小索引肯定能在一個根頁中。在圖中,為了清晰起見,它被特意分佈在幾頁中。
從這個圖表中可以清楚地看出,通過諸如«class = 3»(僅通過第一個欄位進行搜尋)或«class = 3和model = 'Boeing 777-300'»(通過兩個欄位進行搜尋)這樣的謂詞進行搜尋將會非常有效。
然而,根據謂詞«model = 'Boeing 777-300'»進行搜尋的效率會低得多:從根節點開始,我們無法確定要向下搜尋到哪個子節點,因此,我們將不得不向下搜尋所有子節點。這並不意味著像這樣的索引永遠不能使用——它的效率是一個問題。例如,如果我們有三個級別的飛機,每個級別有很多模型,我們將不得不瀏覽索引的大約三分之一,這可能比全表掃描更有效率……或者低效。
但是,如果我們建立一個這樣的索引:
demo=# create index on aircrafts( model, (case when range < 4000 then 1 when range < 10000 then 2 else 3 end));
欄位的順序將改變:
NULL值
btree訪問方法會索引空值,並支援按條件is null和is not null進行搜尋。
demo=# create index on flights(actual_arrival); demo=# explain(costs off) select * from flights where actual_arrival is null; QUERY PLAN ------------------------------------------------------- Bitmap Heap Scan on flights Recheck Cond: (actual_arrival IS NULL) -> Bitmap Index Scan on flights_actual_arrival_idx Index Cond: (actual_arrival IS NULL) (4 rows)
null值位於葉節點的一端或另一端,具體取決於建立索引的方式(null first,或null last)。如果查詢包含排序,這一點很重要:如果SELECT命令在其order BY子句中指定的null值順序與為構建索引指定的順序相同(先為空或後為空),則可以使用索引。
在下面的例子中,這些順序是相同的,因此,我們可以使用索引:
demo=# explain(costs off) select * from flights order by actual_arrival NULLS LAST; QUERY PLAN -------------------------------------------------------- Index Scan using flights_actual_arrival_idx on flights (1 row)
而這裡這些順序是不同的,優化器選擇順序掃描與後續排序:
demo=# explain(costs off) select * from flights order by actual_arrival NULLS FIRST; QUERY PLAN ---------------------------------------- Sort Sort Key: actual_arrival NULLS FIRST -> Seq Scan on flights (3 rows)
要使用索引,它必須在開始處設定null值:
demo=# create index flights_nulls_first_idx on flights(actual_arrival NULLS FIRST); demo=# explain(costs off) select * from flights order by actual_arrival NULLS FIRST; QUERY PLAN ----------------------------------------------------- Index Scan using flights_nulls_first_idx on flights (1 row)
這樣的問題肯定是由於nulls無法排序造成的,也就是說,NULL和其他值的比較結果是未定義的:
demo=# \pset null NULL demo=# select null < 42; ?column? ---------- NULL (1 row)
這與b-樹的概念背道而馳,也不適合一般的模式。然而,null在資料庫中扮演著如此重要的角色,以至於我們總是不得不為它們設定例外。
因為可以對null進行索引,所以即使在表上不施加任何條件,也可以使用索引(因為索引肯定包含表中所有行上的資訊)。如果查詢需要資料排序,並且索引確保所需的順序,那麼這樣做是有意義的。在這種情況下,計劃器可以選擇索引訪問來節省單獨排序。
屬性
讓我們看看«btree»訪問方法的屬性(已經提供了查詢)。
postgres=# select a.amname, p.name, pg_indexam_has_property(a.oid,p.name) from pg_am a, unnest(array['can_order','can_unique','can_multi_col','can_exclude']) p(name) where a.amname = 'btree' order by a.amname; amname | name | pg_indexam_has_property --------+---------------+------------------------- btree | can_order | t btree | can_unique | t btree | can_multi_col | t btree | can_exclude | t (4 rows)
正如我們所見,B-tree可以對資料進行排序並支援唯一性——這是為我們提供這些屬性的唯一訪問方法。還允許使用多列索引,但是其他訪問方法(儘管不是所有方法)可能也支援這樣的索引。我們將在下次討論對排除約束的支援。
postgres=# select p.name, pg_index_has_property('t_a_idx'::regclass,p.name) from unnest(array[ 'clusterable','index_scan','bitmap_scan','backward_scan' ]) p(name); name | pg_index_has_property ---------------+----------------------- clusterable | t index_scan | t bitmap_scan | t backward_scan | t (4 rows)
«btree»訪問方法支援兩種獲取值的技術:索引掃描和點陣圖掃描。正如我們所看到的,訪問方法可以在樹遍歷過程中«forward»和«backward»
postgres=# select p.name, pg_index_column_has_property('t_a_idx'::regclass,1,p.name) from unnest(array[ 'asc','desc','nulls_first','nulls_last','orderable','distance_orderable', 'returnable','search_array','search_nulls' ]) p(name); name | pg_index_column_has_property --------------------+------------------------------ asc | t desc | f nulls_first | f nulls_last | t orderable | t distance_orderable | f returnable | t search_array | t search_nulls | t (9 rows)
該層的前四個屬性解釋了特定列的值是如何精確排序的。在這個例子中,值按升序排序(«asc»),最後提供null值(«nulls_last»)。但是正如我們已經看到的,其他的組合是可能的。
«search_array»屬性表示通過索引支援這樣的表示式:
demo=# explain(costs off) select * from aircrafts where aircraft_code in ('733','763','773'); QUERY PLAN ----------------------------------------------------------------- Index Scan using aircrafts_pkey on aircrafts Index Cond: (aircraft_code = ANY ('{733,763,773}'::bpchar[])) (2 rows)
«returnable»屬性表示支援index-only掃描,這是合理的,因為索引行本身儲存索引值(例如,與雜湊索引不同)。這裡有必要談一談基於b樹的索引覆蓋。
具有附加行的惟一索引(Unique indexes with additional rows)
正如我們前面所討論的,覆蓋索引是儲存查詢所需的所有值的索引,它不需要(幾乎)訪問表本身。
但是,讓我們假設我們想要為唯一索引新增查詢所需的額外列。但是,這種組合值的唯一性並不能保證鍵的唯一性,因此將需要同一列上的兩個索引:一個惟一用於支援完整性約束,另一個惟一用於覆蓋。這肯定是低效的。
在我們公司,Anastasiya Lubennikova lubennikovaav改進了«btree»方法,使得附加的、非惟一的列可以包含在惟一索引中。我們希望這個補丁能被社群採納,成為PostgreSQL的一部分,但這不會在第10版出現。在這一點上,補丁是可用的專業標準9.5+,它看起來是這樣的。
事實上,這個補丁是提交給PostgreSQL 11的。
讓我們考慮一下預訂表:
demo=# \d bookings Table "bookings.bookings" Column | Type | Modifiers --------------+--------------------------+----------- book_ref | character(6) | not null book_date | timestamp with time zone | not null total_amount | numeric(10,2) | not null Indexes: "bookings_pkey" PRIMARY KEY, btree (book_ref) Referenced by: TABLE "tickets" CONSTRAINT "tickets_book_ref_fkey" FOREIGN KEY (book_ref) REFERENCES bookings(book_ref)
在這個表中,主鍵(book_ref,booking code)是由一個常規的«btree»索引提供的。讓我們用一個額外的列建立一個新的唯一索引:
demo=# create unique index bookings_pkey2 on bookings(book_ref) INCLUDE (book_date);
現在我們用一個新的索引替換現有的索引(在事務中,同時應用所有的變化):
demo=# begin; demo=# alter table bookings drop constraint bookings_pkey cascade; demo=# alter table bookings add primary key using index bookings_pkey2; demo=# alter table tickets add foreign key (book_ref) references bookings (book_ref); demo=# commit;
這是我們得到的:
demo=# \d bookings Table "bookings.bookings" Column | Type | Modifiers --------------+--------------------------+----------- book_ref | character(6) | not null book_date | timestamp with time zone | not null total_amount | numeric(10,2) | not null Indexes: "bookings_pkey2" PRIMARY KEY, btree (book_ref) INCLUDE (book_date) Referenced by: TABLE "tickets" CONSTRAINT "tickets_book_ref_fkey" FOREIGN KEY (book_ref) REFERENCES bookings(book_ref)
現在一個索引作為唯一性約束,並作為這個查詢的覆蓋索引,例如:
demo=# explain(costs off) select book_ref, book_date from bookings where book_ref = '059FC4'; QUERY PLAN -------------------------------------------------- Index Only Scan using bookings_pkey2 on bookings Index Cond: (book_ref = '059FC4'::bpchar) (2 rows)
索引的建立
眾所周知,但同樣重要的是,對於一個大型表,最好在沒有索引的情況下載入資料,然後再建立所需的索引。這樣不僅速度更快,而且索引的空間大小很可能更小。
問題在於,建立«btree»索引使用了一種比按行向樹中插入值更有效的過程。粗略地說,表中所有可用的資料都被排序,並建立這些資料的葉。然後內部頁被“建立在”這個基礎上,直到整個金字塔都收斂到根。
這個過程的速度取決於可用RAM的大小,而可用RAM的大小受到«maintenance_work_mem»引數的限制。因此,增大引數值可以加快處理速度。對於唯一索引,除了«maintenance_work_mem»外,還要分配大小«work_mem»的記憶體。
比較語義
上次我們提到過,PostgreSQL需要知道對不同型別的值呼叫哪個雜湊函式,以及這種關聯儲存在«雜湊»訪問方法中。同樣,系統必須弄清楚如何對值進行排序。這在排序、分組(有時)、合併和連線等操作中是必需的。PostgreSQL不會將自己繫結到操作符名稱(比如>、<、=),因為使用者可以定義自己的資料型別,併為相應的操作符提供不同的名稱。由«btree»訪問方法使用的操作符家族定義了操作符名稱。
例如,這些比較運算子用於«bool_ops»運算子族:
postgres=# select amop.amopopr::regoperator as opfamily_operator, amop.amopstrategy from pg_am am, pg_opfamily opf, pg_amop amop where opf.opfmethod = am.oid and amop.amopfamily = opf.oid and am.amname = 'btree' and opf.opfname = 'bool_ops' order by amopstrategy; opfamily_operator | amopstrategy ---------------------+-------------- <(boolean,boolean) | 1 <=(boolean,boolean) | 2 =(boolean,boolean) | 3 >=(boolean,boolean) | 4 >(boolean,boolean) | 5 (5 rows)
在這裡我們可以看到五個比較運算子,但是正如前面提到的,我們不應該依賴它們的名字。為了弄清每個操作符做哪些比較,引入了策略概念。定義了五種策略來描述操作符語義:
·1--less
·2--less or equal
·3--equal
·4--greater or equal
·5--greater
一些操作符族可以包含實現一個策略的多個操作符。例如,«integer_ops»運算子族包含策略1的以下運算子:
postgres=# select amop.amopopr::regoperator as opfamily_operator from pg_am am, pg_opfamily opf, pg_amop amop where opf.opfmethod = am.oid and amop.amopfamily = opf.oid and am.amname = 'btree' and opf.opfname = 'integer_ops' and amop.amopstrategy = 1 order by opfamily_operator; opfamily_operator ---------------------- <(integer,bigint) <(smallint,smallint) <(integer,integer) <(bigint,bigint) <(bigint,integer) <(smallint,integer) <(integer,smallint) <(smallint,bigint) <(bigint,smallint) (9 rows)
由於這一點,在比較一個操作符族中包含的不同型別的值時,優化器可以避免型別強制轉換。
支援新資料型別的索引
文件(https://postgrespro.com/docs/postgrespro/9.6/xindex)提供了為複數建立新資料型別的示例,以及為此類值排序的操作符類的示例。這個例子使用C語言,當速度非常關鍵時,這是絕對合理的。但是,為了更好地理解比較語義,我們可以在同樣的實驗中使用純SQL。
讓我們建立一個包含兩個欄位的新組合型別:實部和虛部:
postgres=# create type complex as (re float, im float);
我們可以建立一個具有新型別欄位的表,並向表中新增一些值:
postgres=# create table numbers(x complex); postgres=# insert into numbers values ((0.0, 10.0)), ((1.0, 3.0)), ((1.0, 1.0));
現在一個問題出現了:如果複數在數學意義上沒有定義階關係,如何對它們進行序?
結果是,比較運算子已經為我們定義了:
postgres=# select * from numbers order by x; x -------- (0,10) (1,1) (1,3) (3 rows)
預設情況下,組合型別的排序是按元件方式進行的:比較第一個欄位,然後比較第二個欄位,依此類推,其方式與逐個字元比較文字字串大致相同。但是我們可以定義不同的順序。例如,複數可以被當作向量,用模(長度)來排序,模(長度)是用座標平方和的平方根來計算的(勾股定理)。為了定義這樣的順序,讓我們建立一個輔助函式,計算模數:
postgres=# create function modulus(a complex) returns float as $$ select sqrt(a.re*a.re + a.im*a.im); $$ immutable language sql;
現在我們用這個輔助函式系統地為這五個比較運算子定義函式:
postgres=# create function complex_lt(a complex, b complex) returns boolean as $$ select modulus(a) < modulus(b); $$ immutable language sql; postgres=# create function complex_le(a complex, b complex) returns boolean as $$ select modulus(a) <= modulus(b); $$ immutable language sql; postgres=# create function complex_eq(a complex, b complex) returns boolean as $$ select modulus(a) = modulus(b); $$ immutable language sql; postgres=# create function complex_ge(a complex, b complex) returns boolean as $$ select modulus(a) >= modulus(b); $$ immutable language sql; postgres=# create function complex_gt(a complex, b complex) returns boolean as $$ select modulus(a) > modulus(b); $$ immutable language sql;
我們會建立相應的運算子。為了說明它們不需要被稱為“>”、“<”等等,讓我們給它們命名比較«weird»。
postgres=# create operator #<#(leftarg=complex, rightarg=complex, procedure=complex_lt); postgres=# create operator #<=#(leftarg=complex, rightarg=complex, procedure=complex_le); postgres=# create operator #=#(leftarg=complex, rightarg=complex, procedure=complex_eq); postgres=# create operator #>=#(leftarg=complex, rightarg=complex, procedure=complex_ge); postgres=# create operator #>#(leftarg=complex, rightarg=complex, procedure=complex_gt);
這樣,我們可以比較數字:
postgres=# select (1.0,1.0)::complex #<# (1.0,3.0)::complex; ?column? ---------- t (1 row)
除了五個操作符之外,«btree»訪問方法還需要定義一個函式(過多但方便):如果第一個值小於、等於或大於第二個值,它必須返回-1、0或1。這個輔助函式稱為support。其他訪問方法可能需要定義其他support函式。
postgres=# create function complex_cmp(a complex, b complex) returns integer as $$ select case when modulus(a) < modulus(b) then -1 when modulus(a) > modulus(b) then 1 else 0 end; $$ language sql;
現在我們準備建立一個操作符類(將自動建立相同名稱的操作符族):
postgres=# create operator class complex_ops default for type complex using btree as operator 1 #<#, operator 2 #<=#, operator 3 #=#, operator 4 #>=#, operator 5 #>#, function 1 complex_cmp(complex,complex);
以下是排序:
postgres=# select * from numbers order by x; x -------- (1,1) (1,3) (0,10) (3 rows)
而且它肯定會被«btree»索引所支援。
您可以通過此查詢獲得支援功能:
postgres=# select amp.amprocnum, amp.amproc, amp.amproclefttype::regtype, amp.amprocrighttype::regtype from pg_opfamily opf, pg_am am, pg_amproc amp where opf.opfname = 'complex_ops' and opf.opfmethod = am.oid and am.amname = 'btree' and amp.amprocfamily = opf.oid; amprocnum | amproc | amproclefttype | amprocrighttype -----------+-------------+----------------+----------------- 1 | complex_cmp | complex | complex (1 row)
內部原理
我們可以使用«pageinspect»擴充套件來探索b-樹的內部結構
demo=# create extension pageinspect;
索引元資料頁:
demo=# select * from bt_metap('ticket_flights_pkey'); magic | version | root | level | fastroot | fastlevel --------+---------+------+-------+----------+----------- 340322 | 2 | 164 | 2 | 164 | 2 (1 row)
這裡最有趣的是索引層級:對於一個有一百萬行的表,兩個列上的索引只需要2層(不包括root)。
第164塊(根)的統計資訊:
demo=# select type, live_items, dead_items, avg_item_size, page_size, free_size from bt_page_stats('ticket_flights_pkey',164); type | live_items | dead_items | avg_item_size | page_size | free_size ------+------------+------------+---------------+-----------+----------- r | 33 | 0 | 31 | 8192 | 6984 (1 row)
塊中的資料(«data»欄位在這裡犧牲了螢幕寬度,包含了索引鍵的二進位制表示值):
demo=# select itemoffset, ctid, itemlen, left(data,56) as data from bt_page_items('ticket_flights_pkey',164) limit 5; itemoffset | ctid | itemlen | data ------------+---------+---------+---------------------------------------------------------- 1 | (3,1) | 8 | 2 | (163,1) | 32 | 1d 30 30 30 35 34 33 32 33 30 35 37 37 31 00 00 ff 5f 00 3 | (323,1) | 32 | 1d 30 30 30 35 34 33 32 34 32 33 36 36 32 00 00 4f 78 00 4 | (482,1) | 32 | 1d 30 30 30 35 34 33 32 35 33 30 38 39 33 00 00 4d 1e 00 5 | (641,1) | 32 | 1d 30 30 30 35 34 33 32 36 35 35 37 38 35 00 00 2b 09 00 (5 rows)
第一個元素與技術有關,並指定塊中所有元素的上限(我們沒有討論實現細節),而資料本身從第二個元素開始。很明顯,最左邊的子節點是塊163,然後是代323,依此類推。反過來,也可以使用相同的函式來研究它們。
還有一個可能有用的擴充套件是“amcheck”,它將被合併到PostgreSQL 10中,更低的版本可以從github獲得。這個擴充套件檢查b-樹中資料的邏輯一致性,並使我們能夠提前檢測故障。