PostgreSQL的B-tree索引

結(jié)構(gòu)

B-tree索引適合用于存儲(chǔ)排序的數(shù)據(jù)橙喘。對(duì)于這種數(shù)據(jù)類型需要定義大于粤咪、大于等于、小于渴杆、小于等于操作符寥枝。

通常情況下,B-tree的索引記錄存儲(chǔ)在數(shù)據(jù)頁(yè)中磁奖。葉子頁(yè)中的記錄包含索引數(shù)據(jù)(keys)以及指向heap tuple記錄(即表的行記錄TIDs)的指針囊拜。內(nèi)部頁(yè)中的記錄包含指向索引子頁(yè)的指針和子頁(yè)中最小值。

B-tree有幾點(diǎn)重要的特性:

1比搭、B-tree是平衡樹(shù)冠跷,即每個(gè)葉子頁(yè)到root頁(yè)中間有相同個(gè)數(shù)的內(nèi)部頁(yè)。因此查詢?nèi)魏我粋€(gè)值的時(shí)間是相同的。

2蜜托、B-tree中一個(gè)節(jié)點(diǎn)有多個(gè)分支抄囚,即每頁(yè)(通常8KB)具有許多TIDs。因此B-tree的高度比較低橄务,通常4到5層就可以存儲(chǔ)大量行記錄幔托。

3、索引中的數(shù)據(jù)以非遞減的順序存儲(chǔ)(頁(yè)之間以及頁(yè)內(nèi)都是這種順序)蜂挪,同級(jí)的數(shù)據(jù)頁(yè)由雙向鏈表連接重挑。因此不需要每次都返回root,通過(guò)遍歷鏈表就可以獲取一個(gè)有序的數(shù)據(jù)集棠涮。

下面是一個(gè)索引的簡(jiǎn)單例子谬哀,該索引存儲(chǔ)的記錄為整型并只有一個(gè)字段:

該索引最頂層的頁(yè)是元數(shù)據(jù)頁(yè),該數(shù)據(jù)頁(yè)存儲(chǔ)索引root頁(yè)的相關(guān)信息严肪。內(nèi)部節(jié)點(diǎn)位于root下面史煎,葉子頁(yè)位于最下面一層。向下的箭頭表示由葉子節(jié)點(diǎn)指向表記錄(TIDs)驳糯。

等值查詢

例如通過(guò)"indexed-field?=?expression"形式的條件查詢49這個(gè)值篇梭。

root節(jié)點(diǎn)有三個(gè)記錄:(4,32,64)。從root節(jié)點(diǎn)開(kāi)始進(jìn)行搜索结窘,由于32≤ 49 < 64很洋,所以選擇32這個(gè)值進(jìn)入其子節(jié)點(diǎn)。通過(guò)同樣的方法繼續(xù)向下進(jìn)行搜索一直到葉子節(jié)點(diǎn)隧枫,最后查詢到49這個(gè)值喉磁。

實(shí)際上,查詢算法遠(yuǎn)不止看上去的這么簡(jiǎn)單官脓。比如协怒,該索引是非唯一索引時(shí),允許存在許多相同值的記錄卑笨,并且這些相同的記錄不止存放在一個(gè)頁(yè)中孕暇。此時(shí)該如何查詢?我們返回到上面的的例子赤兴,定位到第二層節(jié)點(diǎn)(32,43,49)妖滔。如果選擇49這個(gè)值并向下進(jìn)入其子節(jié)點(diǎn)搜索,就會(huì)跳過(guò)前一個(gè)葉子頁(yè)中的49這個(gè)值桶良。因此座舍,在內(nèi)部節(jié)點(diǎn)進(jìn)行等值查詢49時(shí),定位到49這個(gè)值陨帆,然后選擇49的前一個(gè)值43曲秉,向下進(jìn)入其子節(jié)點(diǎn)進(jìn)行搜索采蚀。最后,在底層節(jié)點(diǎn)中從左到右進(jìn)行搜索承二。

(另外一個(gè)復(fù)雜的地方是榆鼠,查詢的過(guò)程中樹(shù)結(jié)構(gòu)可能會(huì)改變,比如分裂)

非等值查詢

通過(guò)"indexed-field ≤ expression" (or "indexed-field ≥ expression")查詢時(shí)亥鸠,首先通過(guò)"indexed-field?=?expression"形式進(jìn)行等值(如果存在該值)查詢妆够,定位到葉子節(jié)點(diǎn)后,再向左或向右進(jìn)行遍歷檢索读虏。

下圖是查詢 n ≤ 35的示意圖:

大于和小于可以通過(guò)同樣的方法進(jìn)行查詢责静。查詢時(shí)需要排除等值查詢出的值袁滥。

范圍查詢

?范圍查詢"expression1 ≤ indexed-field ≤ expression2"時(shí)盖桥,需要通過(guò) "expression1 ≤ indexed-field =expression2"找到一匹配值,然后在葉子節(jié)點(diǎn)從左到右進(jìn)行檢索题翻,一直到不滿足"indexed-field ≤ expression2" 的條件為止揩徊;或者反過(guò)來(lái),首先通過(guò)第二個(gè)表達(dá)式進(jìn)行檢索嵌赠,在葉子節(jié)點(diǎn)定位到該值后塑荒,再?gòu)挠蚁蜃筮M(jìn)行檢索,一直到不滿足第一個(gè)表達(dá)式的條件為止姜挺。

下圖是23 ≤ n ≤ 64的查詢示意圖:

案例

下面是一個(gè)查詢計(jì)劃的實(shí)例齿税。通過(guò)demo database中的aircraft表進(jìn)行介紹。該表有9行數(shù)據(jù)炊豪,由于整個(gè)表只有一個(gè)數(shù)據(jù)頁(yè)凌箕,所以執(zhí)行計(jì)劃不會(huì)使用索引。為了解釋說(shuō)明問(wèn)題词渤,我們使用整個(gè)表進(jìn)行說(shuō)明牵舱。

demo=#select*fromaircrafts;

?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=#createindexonaircrafts(range);

demo=#setenable_seqscan =off;

(更準(zhǔn)確的方式:create index on aircrafts using btree(range),創(chuàng)建索引時(shí)默認(rèn)構(gòu)建B-tree索引缺虐。)

等值查詢的執(zhí)行計(jì)劃:

demo=#explain(costsoff)select*fromaircraftswhererange=3000;

????????????????????QUERY PLAN ????????????????????

---------------------------------------------------

?Index Scan using aircrafts_range_idx on aircrafts

???Index Cond: (range = 3000)

(2 rows)

非等值查詢的執(zhí)行計(jì)劃:

demo=#explain(costsoff)select*fromaircraftswhererange<3000;

????????????????????QUERY PLAN ???????????????????

---------------------------------------------------

?Index Scan using aircrafts_range_idx on aircrafts

???Index Cond: (range < 3000)

(2 rows)

范圍查詢的執(zhí)行計(jì)劃:

demo=#explain(costsoff)select*fromaircrafts

whererangebetween3000and5000;

?????????????????????QUERY PLAN ?????????????????????

-----------------------------------------------------

?Index Scan using aircrafts_range_idx on aircrafts

???Index Cond: ((range >= 3000) AND (range <= 5000))

(2 rows)

排序

再次強(qiáng)調(diào)芜壁,通過(guò)index、index-only或bitmap掃描高氮,btree訪問(wèn)方法可以返回有序的數(shù)據(jù)慧妄。因此如果表的排序條件上有索引,優(yōu)化器會(huì)考慮以下方式:表的索引掃描剪芍;表的順序掃描然后對(duì)結(jié)果集進(jìn)行排序塞淹。

排序順序

當(dāng)創(chuàng)建索引時(shí)可以明確指定排序順序。如下所示紊浩,在range列上建立一個(gè)索引窖铡,并且排序順序?yàn)榻敌颍?/p>

demo=#createindexonaircrafts(rangedesc);

本案例中疗锐,大值會(huì)出現(xiàn)在樹(shù)的左邊,小值出現(xiàn)在右邊费彼。為什么有這樣的需求滑臊?這樣做是為了多列索引。創(chuàng)建aircraft的一個(gè)視圖箍铲,通過(guò)range分成3部分:

demo=#createviewaircrafts_vas

selectmodel,

case

whenrange<4000then1

whenrange<10000then2

else3

endasclass

fromaircrafts;

demo=#select*fromaircrafts_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)

然后創(chuàng)建一個(gè)索引(使用下面表達(dá)式):

demo=#createindexonaircrafts(

(casewhenrange<4000then1whenrange<10000then2else3end),

model);

現(xiàn)在雇卷,可以通過(guò)索引以升序的方式獲取排序的數(shù)據(jù):

demo=#selectclass,modelfromaircrafts_vorderbyclass,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(costsoff)

selectclass,modelfromaircrafts_vorderbyclass,model;

???????????????????????QUERY PLAN ??????????????????????

--------------------------------------------------------

?Index Scan using aircrafts_case_model_idx on aircrafts

(1 row)

同樣,可以以降序的方式獲取排序的數(shù)據(jù):

demo=#selectclass,modelfromaircrafts_vorderbyclassdesc,modeldesc;

?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(costsoff)

selectclass,modelfromaircrafts_vorderbyclassdesc,modeldesc;

???????????????????????????QUERY PLAN ???????????????????????????

-----------------------------------------------------------------

?Index Scan BACKWARD using aircrafts_case_model_idx on aircrafts

(1 row)

然而颠猴,如果一列以升序一列以降序的方式獲取排序的數(shù)據(jù)的話关划,就不能使用索引,只能單獨(dú)排序:

demo=#explain(costsoff)

selectclass,modelfromaircrafts_vorderbyclassASC,modelDESC;

???????????????????QUERY PLAN ???????????????????

-------------------------------------------------

?Sort

Sort Key: (CASE ...END), aircrafts.modelDESC

-> ?SeqScanonaircrafts

(3rows)

(注意翘瓮,最終執(zhí)行計(jì)劃會(huì)選擇順序掃描贮折,忽略之前設(shè)置的enable_seqscan = off。因?yàn)檫@個(gè)設(shè)置并不會(huì)放棄表掃描资盅,只是設(shè)置他的成本----查看costs on的執(zhí)行計(jì)劃)

若有使用索引调榄,創(chuàng)建索引時(shí)指定排序的方向:

demo=#createindexaircrafts_case_asc_model_desc_idxonaircrafts(

(case

whenrange<4000then1

whenrange<10000then2

else3

end)ASC,

modelDESC);

demo=#explain(costsoff)

selectclass,modelfromaircrafts_vorderbyclassASC,modelDESC;

???????????????????????????QUERY PLAN ???????????????????????????

-----------------------------------------------------------------

?Index Scan using aircrafts_case_asc_model_desc_idx on aircrafts

(1 row)

列的順序

當(dāng)使用多列索引時(shí)與列的順序有關(guān)的問(wèn)題會(huì)顯示出來(lái)。對(duì)于B-tree呵扛,這個(gè)順序非常重要:頁(yè)中的數(shù)據(jù)先以第一個(gè)字段進(jìn)行排序每庆,然后再第二個(gè)字段,以此類推今穿。

下圖是在range和model列上構(gòu)建的索引:


當(dāng)然缤灵,上圖這么小的索引在一個(gè)root頁(yè)足以存放。但是為了清晰起見(jiàn)蓝晒,特意將其分成幾頁(yè)腮出。

從圖中可見(jiàn),通過(guò)類似的謂詞class = 3(僅按第一個(gè)字段進(jìn)行搜索)或者class = 3 and model = 'Boeing 777-300'(按兩個(gè)字段進(jìn)行搜索)將非常高效拔创。

然而利诺,通過(guò)謂詞model = 'Boeing 777-300'進(jìn)行搜索的效率將大大降低:從root開(kāi)始,判斷不出選擇哪個(gè)子節(jié)點(diǎn)進(jìn)行向下搜索剩燥,因此會(huì)遍歷所有子節(jié)點(diǎn)向下進(jìn)行搜索慢逾。這并不意味著永遠(yuǎn)無(wú)法使用這樣的索引----它的效率有問(wèn)題。例如灭红,如果aircraft有3個(gè)classes值侣滩,每個(gè)class類中有許多model值,此時(shí)不得不掃描索引1/3的數(shù)據(jù)变擒,這可能比全表掃描更有效君珠。

但是,當(dāng)創(chuàng)建如下索引時(shí):

demo=#createindexonaircrafts(

model,

(casewhenrange<4000then1whenrange<10000then2else3end));

索引字段的順序會(huì)改變:


通過(guò)這個(gè)索引娇斑,model = 'Boeing 777-300'將會(huì)很有效策添,但class = 3則沒(méi)這么高效材部。

NULLs

PostgreSQL的B-tree支持在NULLs上創(chuàng)建索引,可以通過(guò)IS NULL或者IS NOT NULL的條件進(jìn)行查詢唯竹。

考慮flights表乐导,允許NULLs:

demo=#createindexonflights(actual_arrival);

demo=#explain(costsoff)select*fromflightswhereactual_arrivalisnull;

??????????????????????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)

NULLs位于葉子節(jié)點(diǎn)的一端或另一端,這依賴于索引的創(chuàng)建方式(NULLS FIRST或NULLS LAST)浸颓。如果查詢中包含排序物臂,這就顯得很重要了:如果SELECT語(yǔ)句在ORDER BY子句中指定NULLs的順序索引構(gòu)建的順序一樣(NULLS FIRST或NULLS LAST),就可以使用整個(gè)索引产上。

下面的例子中棵磷,他們的順序相同,因此可以使用索引:

demo=#explain(costsoff)

select*fromflightsorderbyactual_arrivalNULLSLAST;

???????????????????????QUERY PLAN ?????????????????????

--------------------------------------------------------

?Index Scan using flights_actual_arrival_idx on flights

(1 row)

下面的例子晋涣,順序不同仪媒,優(yōu)化器選擇順序掃描然后進(jìn)行排序:

demo=#explain(costsoff)

select*fromflightsorderbyactual_arrivalNULLSFIRST;

???????????????QUERY PLAN ?????????????

----------------------------------------

?Sort

???Sort Key: actual_arrival NULLS FIRST

???-> ?Seq Scan on flights

(3 rows)

NULLs必須位于開(kāi)頭才能使用索引:

demo=#createindexflights_nulls_first_idxonflights(actual_arrivalNULLSFIRST);

demo=#explain(costsoff)

select*fromflightsorderbyactual_arrivalNULLSFIRST;

?????????????????????QUERY PLAN ?????????????????????

-----------------------------------------------------

?Index Scan using flights_nulls_first_idx on flights

(1 row)

像這樣的問(wèn)題是由NULLs引起的而不是無(wú)法排序,也就是說(shuō)NULL和其他這比較的結(jié)果無(wú)法預(yù)知:

demo=# \pset null NULL

demo=#selectnull<42;

??column?

----------

?NULL

(1 row)

這和B-tree的概念背道而馳并且不符合一般的模式姻僧。然而NULLs在數(shù)據(jù)庫(kù)中扮演者很重要的角色规丽,因此不得不為NULL做特殊設(shè)置蒲牧。

由于NULLs可以被索引撇贺,因此即使表上沒(méi)有任何標(biāo)記也可以使用索引。(因?yàn)檫@個(gè)索引包含表航記錄的所有信息)冰抢。如果查詢需要排序的數(shù)據(jù)松嘶,而且索引確保了所需的順序,那么這可能是由意義的挎扰。這種情況下翠订,查詢計(jì)劃更傾向于通過(guò)索引獲取數(shù)據(jù)。

屬性

下面介紹btree訪問(wèn)方法的特性遵倦。

?amname | ????name ?????| pg_indexam_has_property

--------+---------------+-------------------------

?btree ?| can_order ????| t

?btree ?| can_unique ???| t

?btree ?| can_multi_col | t

?btree ?| can_exclude ??| t

可以看到尽超,B-tree能夠排序數(shù)據(jù)并且支持唯一性。同時(shí)還支持多列索引梧躺,但是其他訪問(wèn)方法也支持這種索引似谁。我們將在下次討論EXCLUDE條件。

?????name ?????| pg_index_has_property

---------------+-----------------------

?clusterable ??| t

?index_scan ???| t

?bitmap_scan ??| t

?backward_scan | t

Btree訪問(wèn)方法可以通過(guò)以下兩種方式獲取數(shù)據(jù):index scan以及bitmap scan掠哥」ぃ可以看到,通過(guò)tree可以向前和向后進(jìn)行遍歷续搀。

??????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

前四種特性指定了特定列如何精確的排序塞琼。本案例中,值以升序(asc)進(jìn)行排序并且NULLs在后面(nulls_last)禁舷。也可以有其他組合彪杉。

search_array的特性支持向這樣的表達(dá)式:

demo=#explain(costsoff)

select*fromaircraftswhereaircraft_codein('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 scan毅往,由于索引本身也存儲(chǔ)索引值所以這是合理的。下面簡(jiǎn)單介紹基于B-tree的覆蓋索引派近。

具有額外列的唯一索引

前面討論了:覆蓋索引包含查詢所需的所有值煞抬,需不要再回表。唯一索引可以成為覆蓋索引构哺。

假設(shè)我們查詢所需要的列添加到唯一索引革答,新的組合唯一鍵可能不再唯一,同一列上將需要2個(gè)索引:一個(gè)唯一曙强,支持完整性約束残拐;另一個(gè)是非唯一,為了覆蓋索引碟嘴。這當(dāng)然是低效的溪食。

在我們公司 Anastasiya Lubennikova @ lubennikovaav 改進(jìn)了btree,額外的非唯一列可以包含在唯一索引中娜扇。我們希望這個(gè)補(bǔ)丁可以被社區(qū)采納错沃。實(shí)際上PostgreSQL11已經(jīng)合了該補(bǔ)丁。

考慮表bookings:d

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)

這個(gè)表中雀瓢,主鍵(book_ref,booking code)通過(guò)常規(guī)的btree索引提供枢析,下面創(chuàng)建一個(gè)由額外列的唯一索引:

demo=#createuniqueindexbookings_pkey2onbookings(book_ref)INCLUDE(book_date);

然后使用新索引替代現(xiàn)有索引:

demo=#begin;

demo=#altertablebookingsdropconstraintbookings_pkeycascade;

demo=#altertablebookingsaddprimarykeyusingindexbookings_pkey2;

demo=#altertableticketsaddforeignkey(book_ref)referencesbookings (book_ref);

demo=#commit;

然后表結(jié)構(gòu):

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)

此時(shí),這個(gè)索引可以作為唯一索引工作也可以作為覆蓋索引:

demo=#explain(costsoff)

selectbook_ref, book_datefrombookingswherebook_ref ='059FC4';

????????????????????QUERY PLAN ???????????????????

--------------------------------------------------

?Index Only Scan using bookings_pkey2 on bookings

???Index Cond: (book_ref = '059FC4'::bpchar)

(2 rows)

創(chuàng)建索引

眾所周知刃麸,對(duì)于大表醒叁,加載數(shù)據(jù)時(shí)最好不要帶索引;加載完成后再創(chuàng)建索引泊业。這樣做不僅提升效率還能節(jié)省空間把沼。

創(chuàng)建B-tree索引比向索引中插入數(shù)據(jù)更高效。所有的數(shù)據(jù)大致上都已排序吁伺,并且數(shù)據(jù)的葉子頁(yè)已創(chuàng)建好饮睬,然后只需構(gòu)建內(nèi)部頁(yè)直到root頁(yè)構(gòu)建成一個(gè)完整的B-tree。

這種方法的速度依賴于RAM的大小篮奄,受限于參數(shù)maintenance_work_mem捆愁。因此增大該參數(shù)值可以提升速度。對(duì)于唯一索引宦搬,除了分配maintenance_work_mem的內(nèi)存外牙瓢,還分配了work_mem的大小的內(nèi)存。

比較

前面间校,提到PG需要知道對(duì)于不同類型的值調(diào)用哪個(gè)函數(shù)矾克,并且這個(gè)關(guān)聯(lián)方法存儲(chǔ)在哈希訪問(wèn)方法中。同樣憔足,系統(tǒng)必須找出如何排序胁附。這在排序酒繁、分組(有時(shí))、merge join中會(huì)涉及控妻。PG不會(huì)將自身綁定到操作符名稱州袒,因?yàn)橛脩艨梢宰远x他們的數(shù)據(jù)類型并給出對(duì)應(yīng)不同的操作符名稱。

例如bool_ops操作符集中的比較操作符:

postgres=#selectamop.amopopr::regoperatorasopfamily_operator,

?????????amop.amopstrategy

frompg_am am,

???????? pg_opfamily opf,

?????????pg_amop amop

whereopf.opfmethod = am.oid

andamop.amopfamily = opf.oid

andam.amname ='btree'

andopf.opfname ='bool_ops'

orderbyamopstrategy;

??opfamily_operator ?| amopstrategy

---------------------+--------------

?<(boolean,boolean) ?| ???????????1

?<=(boolean,boolean) | ???????????2

?=(boolean,boolean) ?| ???????????3

?>=(boolean,boolean) | ???????????4

?>(boolean,boolean) ?| ???????????5

(5 rows)

這里可以看到有5種操作符,但是不應(yīng)該依賴于他們的名字。為了指定哪種操作符做什么操作哟冬,引入策略的概念鹦牛。為了描述操作符語(yǔ)義习柠,定義了5種策略:

? ? ? ? 1 — less

? ? ? ? 2 — less or equal

? ? ? ? 3 — equal

? ? ? ? 4 — greater or equal

? ? ? ? 5?— greater

postgres=#selectamop.amopopr::regoperatorasopfamily_operator

frompg_am am,

?????????pg_opfamily opf,

?????????pg_amop amop

whereopf.opfmethod = am.oid

andamop.amopfamily = opf.oid

andam.amname ='btree'

andopf.opfname ='integer_ops'

andamop.amopstrategy =1

orderbyopfamily_operator;

??pfamily_operator ?

----------------------

?<(integer,bigint)

?<(smallint,smallint)

?<(integer,integer)

?<(bigint,bigint)

?<(bigint,integer)

?<(smallint,integer)

?<(integer,smallint)

?<(smallint,bigint)

?<(bigint,smallint)

(9 rows)

一些操作符族可以包含幾種操作符,例如integer_ops包含策略1的幾種操作符:

正因如此,當(dāng)比較類型在一個(gè)操作符族中時(shí),不同類型值的比較亥至,優(yōu)化器可以避免類型轉(zhuǎn)換。

索引支持的新數(shù)據(jù)類型

文檔中提供了一個(gè)創(chuàng)建符合數(shù)值的新數(shù)據(jù)類型贱迟,以及對(duì)這種類型數(shù)據(jù)進(jìn)行排序的操作符類姐扮。該案例使用C語(yǔ)言完成。但不妨礙我們使用純SQL進(jìn)行對(duì)比試驗(yàn)衣吠。

創(chuàng)建一個(gè)新的組合類型:包含real和imaginary兩個(gè)字段

postgres=#createtypecomplexas(refloat, imfloat);

創(chuàng)建一個(gè)包含該新組合類型字段的表:

postgres=#createtablenumbers(x complex);

postgres=#insertintonumbersvalues((0.0,10.0)), ((1.0,3.0)), ((1.0,1.0));

現(xiàn)在有個(gè)疑問(wèn)茶敏,如果在數(shù)學(xué)上沒(méi)有為他們定義順序關(guān)系,如何進(jìn)行排序蒸播?

已經(jīng)定義好了比較運(yùn)算符:

postgres=#select*fromnumbersorderbyx;

???x ???

--------

?(0,10)

?(1,1)

?(1,3)

(3 rows)

默認(rèn)情況下睡榆,對(duì)于組合類型排序是分開(kāi)的:首先比較第一個(gè)字段然后第二個(gè)字段,與文本字符串比較方法大致相同袍榆。但是我們也可以定義其他的排序方式,例如組合數(shù)字可以當(dāng)做一個(gè)向量塘揣,通過(guò)模值進(jìn)行排序包雀。為了定義這樣的順序,我們需要?jiǎng)?chuàng)建一個(gè)函數(shù):

postgres=#createfunctionmodulus(a complex)returnsfloatas$$

selectsqrt(a.re*a.re + a.im*a.im);

$$ immutable language sql;

//此時(shí)亲铡,使用整個(gè)函數(shù)系統(tǒng)的定義5種操作符:

postgres=#createfunctioncomplex_lt(a complex, b complex)returnsbooleanas$$

selectmodulus(a) < modulus(b);

$$ immutable language sql;

postgres=#createfunctioncomplex_le(a complex, b complex)returnsbooleanas$$

selectmodulus(a) <= modulus(b);

$$ immutable language sql;

postgres=#createfunctioncomplex_eq(a complex, b complex)returnsbooleanas$$

selectmodulus(a) = modulus(b);

$$ immutable language sql;

postgres=#createfunctioncomplex_ge(a complex, b complex)returnsbooleanas$$

selectmodulus(a) >= modulus(b);

$$ immutable language sql;

postgres=#createfunctioncomplex_gt(a complex, b complex)returnsbooleanas$$

selectmodulus(a) > modulus(b);

$$ immutable language sql;

然后創(chuàng)建對(duì)應(yīng)的操作符:

postgres=#createoperator#<#(leftarg=complex, rightarg=complex,procedure=complex_lt);

postgres=#createoperator#<=#(leftarg=complex, rightarg=complex,procedure=complex_le);

postgres=#createoperator#=#(leftarg=complex, rightarg=complex,procedure=complex_eq);

postgres=#createoperator#>=#(leftarg=complex, rightarg=complex,procedure=complex_ge);

postgres=#createoperator#>#(leftarg=complex, rightarg=complex,procedure=complex_gt);

此時(shí)才写,可以比較數(shù)字:

postgres=#select(1.0,1.0)::complex #<# (1.0,3.0)::complex;

??column?

----------

?t

(1 row)

除了整個(gè)5個(gè)操作符,還需要定義函數(shù):小于返回-1奖蔓;等于返回0赞草;大于返回1。其他訪問(wèn)方法可能需要定義其他函數(shù):

postgres=#createfunctioncomplex_cmp(a complex, b complex)returnsintegeras$$

selectcasewhenmodulus(a) < modulus(b)then-1

whenmodulus(a) > modulus(b)then1

else0

end;

$$ language sql;

創(chuàng)建一個(gè)操作符類:

postgres=# createoperatorclass complex_ops

defaultfortypecomplex

usingbtree as

operator1#<#,

operator2#<=#,

operator3#=#,

operator4#>=#,

operator5#>#,

function1complex_cmp(complex,complex);

//排序結(jié)果:

postgres=# select * from numbers order by x;

???x ???

--------

(1,1)

(1,3)

(0,10)

(3rows)

//可以使用此查詢獲取支持的函數(shù):

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'

andopf.opfmethod = am.oid

andam.amname ='btree'

andamp.amprocfamily = opf.oid;

?amprocnum | ??amproc ???| amproclefttype | amprocrighttype

-----------+-------------+----------------+-----------------

1| complex_cmp |complex|complex

(1row)

內(nèi)部結(jié)構(gòu)

使用pageinspect插件觀察B-tree結(jié)構(gòu):

demo=# create extension pageinspect;

索引的元數(shù)據(jù)頁(yè):

demo=#select*frombt_metap('ticket_flights_pkey');

?magic ?| version | root | level | fastroot | fastlevel

--------+---------+------+-------+----------+-----------

?340322 | ??????2 | ?164 | ????2 | ?????164 | ????????2

(1 row)

值得關(guān)注的是索引level:不包括root吆鹤,有一百萬(wàn)行記錄的表其索引只需要2層就可以了厨疙。

Root頁(yè),即164號(hào)頁(yè)面的統(tǒng)計(jì)信息:

demo=#selecttype, live_items, dead_items, avg_item_size, page_size, free_size

frombt_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)

該頁(yè)中數(shù)據(jù):

demo=#selectitemoffset, ctid, itemlen,left(data,56)asdata

frombt_page_items('ticket_flights_pkey',164)limit5;

?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)

第一個(gè)tuple指定該頁(yè)的最大值疑务,真正的數(shù)據(jù)從第二個(gè)tuple開(kāi)始沾凄。很明顯最左邊子節(jié)點(diǎn)的頁(yè)號(hào)是163梗醇,然后是323。反過(guò)來(lái)撒蟀,可以使用相同的函數(shù)搜索叙谨。

PG10版本提供了"amcheck"插件,該插件可以檢測(cè)B-tree數(shù)據(jù)的邏輯一致性保屯,使我們提前探知故障手负。

原文

https://habr.com/en/company/postgrespro/blog/443284/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市姑尺,隨后出現(xiàn)的幾起案子虫溜,更是在濱河造成了極大的恐慌,老刑警劉巖股缸,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衡楞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡敦姻,警方通過(guò)查閱死者的電腦和手機(jī)瘾境,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)镰惦,“玉大人迷守,你說(shuō)我怎么就攤上這事⊥耄” “怎么了兑凿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)茵瘾。 經(jīng)常有香客問(wèn)我礼华,道長(zhǎng),這世上最難降的妖魔是什么拗秘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任圣絮,我火速辦了婚禮,結(jié)果婚禮上雕旨,老公的妹妹穿的比我還像新娘扮匠。我一直安慰自己,他們只是感情好凡涩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布棒搜。 她就那樣靜靜地躺著,像睡著了一般活箕。 火紅的嫁衣襯著肌膚如雪力麸。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音末盔,去河邊找鬼筑舅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛陨舱,可吹牛的內(nèi)容都是我干的翠拣。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼游盲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼误墓!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起益缎,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤谜慌,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后莺奔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體欣范,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年令哟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恼琼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡屏富,死狀恐怖晴竞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情狠半,我是刑警寧澤噩死,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站神年,受9級(jí)特大地震影響已维,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘤袖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一衣摩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捂敌,春花似錦、人聲如沸既琴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)甫恩。三九已至逆济,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奖慌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工抛虫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人简僧。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓建椰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親岛马。 傳聞我的和親對(duì)象是個(gè)殘疾皇子棉姐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 個(gè)人專題目錄[http://www.reibang.com/p/140e2a59db2c] 1. 性能下降SQL...
    Java及SpringBoot閱讀 1,281評(píng)論 0 4
  • 1. 為什么會(huì)有這篇文章? 選擇DB作為T(mén)raining的第一個(gè)系列啦逆,而不是其他伞矩,是因?yàn)檫@貨太重要,而且也是大多數(shù)...
    Ryanwli閱讀 725評(píng)論 0 4
  • Access Method是從9.4版本引入Postgres的夏志。它是Postgres為用戶自定義索引開(kāi)的“后門(mén)”乃坤。...
    Pivotal閱讀 2,099評(píng)論 0 0
  • 現(xiàn)在咱們看一下李萍兒的簡(jiǎn)歷正月十五元宵佳節(jié)生,因?yàn)橛腥思宜土艘粚?duì)魚(yú)瓶?jī)簛?lái)沟蔑,就小字喚瓶姐第一樁婚姻里面湿诊,作為梁中書(shū)的...
    原野草閱讀 639評(píng)論 0 1
  • “留下令自己心動(dòng)的物品枫吧,丟掉不會(huì)令自己心動(dòng)的物品,在反復(fù)自問(wèn)的過(guò)程中宇色,就能夠發(fā)現(xiàn)對(duì)自己而言最重要的東西是什么九杂,最重...
    美麗文靜2閱讀 6,026評(píng)論 2 12