淺談Postgres的Access Method

Access Method是從9.4版本引入Postgres的蕉堰。它是Postgres為用戶自定義索引開的“后門”。本文簡單介紹了access method的一些基本知識再层,為讀者提供一個整體的概念柠逞。

關于PG索引的概念,讀者可以參考之前的文章酿联。

除了官方默認提供的索引種類,PG還給用戶打開了接口夺巩,用戶如果想要一個不一樣的索引贞让,完全可以自己通過寫代碼的方式來定義。

那么柳譬,自定義一個索引喳张,你需要寫哪些函數(shù)、每個函數(shù)的作用又是什么呢征绎?

如何步步為營造出一個屬于自己的索引類型

創(chuàng)建新索引

假設你已經(jīng)有了一堆數(shù)據(jù)蹲姐,并為這堆數(shù)據(jù)造了一個表。現(xiàn)在你需要為它創(chuàng)建一個索引人柿,讓它們能被方便地管理起來。我們這就創(chuàng)建一個新的索引忙厌,用來為這些初始數(shù)據(jù)創(chuàng)建“人口普查記錄”凫岖。

定義ambuild函數(shù),用你希望的方式創(chuàng)建索引逢净。PG可以給你提供的信息有:heap的信息和PG內部index的基本定義哥放。大多數(shù)情況,你是需要調用IndexBuildHeapScan這個函數(shù)來進行第一遍的heap表掃描爹土,來把索引數(shù)據(jù)生成好甥雕。咦?那我們自定義的部分在哪呢胀茵?別急社露,IndexBuildHeapScan這個函數(shù)接受一個回調函數(shù),此回調函數(shù)就是關鍵琼娘。PG會為每個tuple調用一次這個回調函數(shù)峭弟,tuple數(shù)據(jù)的信息會作為參數(shù)傳入,你想要如何為這條數(shù)據(jù)建立索引脱拼,就在這里下達指令吧瞒瘸。

另外PG也提供讓你在沒有數(shù)據(jù)的情況下創(chuàng)建一個空索引的函數(shù)ambuildempty

恭喜你熄浓,現(xiàn)在你已經(jīng)有一個初始的世界(index)情臭,和一些原住居民了(初始的索引數(shù)據(jù))。現(xiàn)在有新生兒出生(表里又有新的數(shù)據(jù)),我們需要為新生兒創(chuàng)建戶口(新的索引數(shù)據(jù))俯在。

aminsert就是創(chuàng)建戶口的辦事員竟秫,需要的參數(shù)有:新數(shù)據(jù)的值、數(shù)據(jù)在heap上的位置朝巫,還有一個選項表示需不需要檢查新增的索引是不是“唯一”的(唯一性檢查的介紹見文末)鸿摇。

你的世界需要新陳代謝,當有數(shù)據(jù)“死亡”的時候劈猿,需要給它辦理死亡證明拙吉。
ambulkdelete,注意新增的時候是一條一條增加揪荣,而刪除的時候可以批量刪除筷黔,返回值是刪除結果的統(tǒng)計信息。如果要刪掉的數(shù)據(jù)太多的時候仗颈,這個函數(shù)可能會分批調用佛舱,不過不用擔心,每一次調用的統(tǒng)計結果返回值都會傳入下一次調用挨决,保證最后統(tǒng)計信息的正確性请祖。

Vacuum

之前介紹索引的時候提到過,PG索引只會在vaccum的時候被刪除脖祈。Access Method里提供一個amvaccumcleanup的函數(shù)肆捕,讓你在刪掉索引之后,能夠在這里把應該做的事情做了盖高,比如對那些已被刪掉的索引占用的空間進行回收慎陵。

Index Property

一個索引有三層屬性,包括索引類型的屬性喻奥、創(chuàng)建的特定索引的屬性席纽、特定索引中一列的屬性。每個具體屬性的含義可參考文章撞蚕。

首先是索引類型的屬性润梯,所有同類型的索引都共享這些屬性。pg提供了一個函數(shù)pg_indexam_has_property以供查詢索引類型屬性诈豌。

/*查詢btree這個索引類型的某些屬性*/
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) 

其次是創(chuàng)建的某特定索引的屬性仆救,這是每個索引的個性。查詢的函數(shù)名是pg_index_has_property矫渔。

/* 查詢名為t_a_idx這個索引的一系列屬性 */
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) 

最后彤蔽,index中每一列都有屬性,查詢函數(shù)是pg_index_column_has_property庙洼。

/* 查詢t_a_idx 中序號為1的列的某些屬性 */
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) 

這三個函數(shù)在access method中都可以用amproperty這個函數(shù)來自定義行為顿痪。

Scan相關

索引scan的過程類似于事務镊辕,也有begin(ambeginscan)、start(amrescan)蚁袭、end(amendscan)三個階段征懈,還支持記錄及恢復scan進行到的位置(ammarkpos、amrestrpos)揩悄。

幾個注意點??:

  • amrescan需要用戶自己對where過濾條件作出合理的判斷和預處理卖哎。比如Where x > 5 AND x > 15這個條件,雖然看似簡單删性,但也不能指望pg來做條件合并亏娜,我們需要自己決定丟掉x > 5這個條件。
  • 排序蹬挺。Access method要支持排序可以有兩種方法维贺,1. btree是天然支持排序的,這時候吧amcanorder(上文的索引類型屬性之一)設成true就可以巴帮。2. 其他的索引溯泣,想要實現(xiàn)排序,則需要將amcanorderbyop設成true榕茧,從名字就能知道垃沦,這時想要返回有序的數(shù)據(jù),需要使用比較operator對index_key進行排序操作用押。

對tuple的操作栏尚,支持兩種方式,plain index scan和bitmap index scan(關于bitmap index scan也請看之前介紹index的文章)只恨。如果支持plain index scan,那么必須提供amgettuple這個函數(shù)抬虽,同樣官觅,如果這個index類型支持bitmap index scan,就要提供amgetbitmap這個函數(shù)的定義阐污。

amgettuple函數(shù)有一個很有意思的參數(shù)叫direction休涤,能支持這個特征的index,可以指定這次取的tuple是“正向”還是“反向”笛辟,如果是反向的功氨,那么返回的就是“最后一個”能match的tuple,而不是第一個手幢。而且捷凄,每一次amgettuple調用,都可以指定和上一次不同的方向围来,雖然我暫時并未想到這個特性有什么實際應用場景跺涤。

amgetbitmap要比amgettuple高效的多匈睁,因為它是批處理的,能減少很多鎖操作桶错。當然航唆,這種情況下我們也不需要什么記錄和恢復scan的位置了,另外批處理沒有方向性院刁,direction也不需要了糯钙。排序?那也是沒有的退腥。

這兩個get函數(shù)任岸,你可以全部實現(xiàn),也可以挑其中一個實現(xiàn)阅虫,具體怎么做完全取決于你的索引的內部結構演闭。

并行scan

有些索引會想要實現(xiàn)并行的scan,同時起多個進程對同一個index進行scan颓帝。Access method提供了下面三個接口函數(shù):

  • amestimateparallelscan米碰,用以計算進行并行scan額外需要的dynamic shared memory的數(shù)量,注意??因為是并行购城,需要進程間的內存共享吕座,所以是shared memory。
  • aminitparallelscan瘪板,用來進行dynamic shared memory初始化的函數(shù)吴趴,如果沒有必要進行內存初始化,可以忽略侮攀。
  • amparallelrescan锣枝,用來重新啟動并行scan的函數(shù),這時所有在共享內存里的數(shù)據(jù)都會被重置兰英。

其他函數(shù)

  • amcanreturn 檢查某個列在這個index中是否支持index only scan撇叁。這個index only scan之前也介紹過,如果本次查詢所有需要的column都存在于index的key中畦贸,那么就可以使用index only scan陨闹。
  • amoptions 向這個index設定一些option參數(shù),隨便舉一個參數(shù)的例子:“autovacuum_enabled = false”薄坏,禁止對索引進行自動vacuum趋厉。

用access method對索引進行更新,必須支持多個session“同時”操作胶坠,這就需要引入鎖君账。在scan的時候,只需要一個讀鎖就可以涵但,pg里用的是AccessShareLock杈绸,而更新的時候帖蔓,用的是RowExclusiveLock,寫鎖的粒度是單行數(shù)據(jù)瞳脓。

對索引進行更新時有幾個必須遵循的規(guī)則:

  1. 先在heap上產(chǎn)生新數(shù)據(jù)塑娇,然后才建索引條目。
  2. 先刪掉索引劫侧,再刪掉heap數(shù)據(jù)埋酬。
  3. 每一個進行中的index scan,當前最后一次調用amgettuple所返回的條目烧栋,它所在的index page都必須被pin住写妥,而且此page里面存儲的所有條目在此時都是不能被刪除的。這主要是出于non-MVCC snapshot的考慮审姓。如果我們pin住了index page珍特,那么索引就不可能被刪除,根據(jù)上一條規(guī)則魔吐,那么heap數(shù)據(jù)也不能被刪除扎筒,就可以避免一個session正在進行index scan,另外一個session把恰好要取的數(shù)據(jù)從heap上刪掉的情況酬姆。然而對于amgetbitmap嗜桌,上文說過這個操作是批處理的,當然也不會pin住index page辞色,所以bitmap scan只可以用在MVCC snapshot上骨宠。

唯一性檢查

注意,目前只有btree索引支持唯一性相满。唯一性意味著一個key值只能對應一條heap數(shù)據(jù)层亿。

其實在物理方面,一個key值只存儲一個條目是不可能的立美。因為我們需要支持MVCC棕所,一個key需要存儲多個版本的條目。所以悯辙,所謂“唯一性”的限制僅限于同一個版本。如果要在支持唯一性的索引中插入新的條目迎吵,我們需要分如下幾種情況考慮:

  1. 發(fā)現(xiàn)了沖突躲撰,但有沖突的條目在當前的transaction中已經(jīng)被刪掉了,這種情況沒問題击费。
  2. 有沖突拢蛋,而且有沖突的條目是另外一個transaction插入的,并且那個transaction還沒有被提交蔫巩。那么我們只能等待谆棱。如果那個transaction被roll back了,那么沖突就不存在了。否則直秆,就會產(chǎn)生唯一性的沖突船庇。
  3. 同樣,發(fā)現(xiàn)了沖突个从,不過有沖突的條目在另一個尚未提交的transaction里被刪掉了脉幢,我們還是需要等待,和2是相同的道理嗦锐。

Tips: create unique index concurrently
對一個很大的表創(chuàng)建索引是很耗時的嫌松,為了能在創(chuàng)建索引的時候不至于長時間block其他需要對同一個表進行寫操作的session,pg提供了concurrently這個關鍵詞奕污。如果用并行模式創(chuàng)建索引萎羔,其他的session可以同時對數(shù)據(jù)表進行增刪改的操作(讀的操作任何時候都不受建索引的影響)。
但是這個特性也給我們的一致性檢查帶來了麻煩碳默。如果我們在并行創(chuàng)建唯一索引時發(fā)現(xiàn)了沖突贾陷,在正式報警之前,需要再次確認有沖突的那一行heap數(shù)據(jù)沒有被concurrent的其他session刪掉腻窒,以防止錯誤報警昵宇。當然,這個再次確認的操作是需要access method自己去做的儿子,在我們的程序里需要顯式寫出去heap查找某一行數(shù)據(jù)狀態(tài)(是不是還活著的)的代碼瓦哎。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市柔逼,隨后出現(xiàn)的幾起案子蒋譬,更是在濱河造成了極大的恐慌,老刑警劉巖愉适,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件犯助,死亡現(xiàn)場離奇詭異,居然都是意外死亡维咸,警方通過查閱死者的電腦和手機剂买,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來癌蓖,“玉大人瞬哼,你說我怎么就攤上這事∽飧保” “怎么了坐慰?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長用僧。 經(jīng)常有香客問我结胀,道長赞咙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任糟港,我火速辦了婚禮攀操,結果婚禮上,老公的妹妹穿的比我還像新娘着逐。我一直安慰自己崔赌,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布耸别。 她就那樣靜靜地躺著健芭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秀姐。 梳的紋絲不亂的頭發(fā)上慈迈,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音省有,去河邊找鬼痒留。 笑死,一個胖子當著我的面吹牛蠢沿,可吹牛的內容都是我干的伸头。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼舷蟀,長吁一口氣:“原來是場噩夢啊……” “哼恤磷!你這毒婦竟也來了?” 一聲冷哼從身側響起野宜,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤扫步,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后匈子,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體河胎,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年虎敦,在試婚紗的時候發(fā)現(xiàn)自己被綠了游岳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡其徙,死狀恐怖吭历,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情擂橘,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布摩骨,位于F島的核電站通贞,受9級特大地震影響朗若,放射性物質發(fā)生泄漏。R本人自食惡果不足惜昌罩,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一哭懈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茎用,春花似錦遣总、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至古涧,卻和暖如春垂券,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背羡滑。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工菇爪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柒昏。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓凳宙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親职祷。 傳聞我的和親對象是個殘疾皇子氏涩,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內容