解析HOT原理

一、疑問(wèn)

前段時(shí)間检访;QQ群里有人對(duì)“這個(gè)表(0,4)這行數(shù)據(jù)我做了update操作罗晕,查看索引的page數(shù)據(jù)千埃,看到索引一直指向(0,4)憔儿,用ctid='(0,4)'查詢業(yè)務(wù)表是查不到數(shù)據(jù)的;然后我做了表的vacuum放可,reindex甚至drop/create index谒臼,還是這樣的”感到疑惑朝刊。

在PostgreSQL8.3實(shí)現(xiàn)了(heap only tuple)HOT特性。它存在的目的就是消除表非索引列更新對(duì)索引影響蜈缤。但是它如何工作的呢拾氓?

二、解析

我們來(lái)模擬環(huán)境

postgres=# create table tbl_hot(id int primary key, info text);
CREATE TABLE
postgres=# insert into tbl_hot select generate_series(1, 4), 'lottu';
INSERT 0 4
postgres=# select ctid ,t.* from tbl_hot t;
 ctid  | id | info  
-------+----+-------
 (0,1) |  1 | lottu
 (0,2) |  2 | lottu
 (0,3) |  3 | lottu
 (0,4) |  4 | lottu
(4 rows)
postgres=# \d tbl_hot
              Table "public.tbl_hot"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 info   | text    |           |          | 
Indexes:
    "tbl_hot_pkey" PRIMARY KEY, btree (id)

我們創(chuàng)建表tbl_hot底哥;并插入4條記錄咙鞍。這是我們更新(0,4)這條記錄。如下

postgres=# update tbl_hot set info = 'rax' where id = 4;
UPDATE 1
postgres=# select ctid ,t.* from tbl_hot t;
 ctid  | id | info  
-------+----+-------
 (0,1) |  1 | lottu
 (0,2) |  2 | lottu
 (0,3) |  3 | lottu
 (0,5) |  4 | rax
(4 rows)

更新之后我們看下索引有變化沒(méi)趾徽?

postgres=# select * from bt_page_items('tbl_hot_pkey', 1);
 itemoffset | ctid  | itemlen | nulls | vars |          data           
------------+-------+---------+-------+------+-------------------------
          1 | (0,1) |      16 | f     | f    | 01 00 00 00 00 00 00 00
          2 | (0,2) |      16 | f     | f    | 02 00 00 00 00 00 00 00
          3 | (0,3) |      16 | f     | f    | 03 00 00 00 00 00 00 00
          4 | (0,4) |      16 | f     | f    | 04 00 00 00 00 00 00 00
(4 rows)

bt_page_items函數(shù)是用來(lái):返回關(guān)于B-樹(shù)索引頁(yè)面上所有項(xiàng)的詳細(xì)信息续滋,在B樹(shù)葉子頁(yè)面中,ctid指向一個(gè)堆元組孵奶。在內(nèi)部頁(yè)面中疲酌,ctid的塊編號(hào)部分指向索引本身中的另一個(gè)頁(yè)面。

我們可以看出索引沒(méi)變化了袁。索引存放是表數(shù)據(jù)的ctid+索引值朗恳。使用索引可以快速找到對(duì)應(yīng)記錄的ctid。現(xiàn)在 記錄id=4 索引的ctid(0,4)跟表對(duì)應(yīng)ctid(0,5)不一致载绿。那是不是索引失效了粥诫。我們來(lái)測(cè)試下:

postgres=# explain select id from tbl_hot where id = 4;
                                   QUERY PLAN                                    
---------------------------------------------------------------------------------
 Index Only Scan using tbl_hot_pkey on tbl_hot  (cost=0.15..8.17 rows=1 width=4)
   Index Cond: (id = 4)
(2 rows)

索引沒(méi)失效;那如何找到對(duì)應(yīng)的記錄呢卢鹦?我們先來(lái)看下表存儲(chǔ)的page情況

get_raw_page: 根據(jù)參數(shù)表明臀脏、數(shù)據(jù)文件類型(main、fsm冀自、vm)以及page位置揉稚,將當(dāng)前表文件中的page內(nèi)容返回。還有一個(gè)函數(shù)于此同名熬粗,只有兩個(gè)參數(shù)搀玖,是將第二個(gè)參數(shù)省略,直接使用'main'驻呐。
heap_page_items: 參數(shù)是函數(shù)get_raw_page的返回值灌诅,返回值是將page內(nèi)的項(xiàng)指針(ItemIddata)以及HeapTupleHeaderData的詳細(xì)信息。
其中理解下下面字段含義
lp:這是插件自己定義的列含末,在源碼中其實(shí)沒(méi)有猜拾,這個(gè)是項(xiàng)指針的順序。
lp_off:tuple在page中的位置
lp_flags: 含義如下
define LP_UNUSED 0 /* unused (should always have lp_len=0) /
define LP_NORMAL 1 /
used (should always have lp_len>0) /
define LP_REDIRECT 2 /
HOT redirect (should have lp_len=0) /
define LP_DEAD 3 /
dead, may or may not have storage /
t_ctid: 這個(gè)是指物理ID
t_infomask2:表字段的個(gè)數(shù)以及一些flags佣盒;其中flag含義
define HEAP_NATTS_MASK 0x07FF
/
11 bits for number of attributes // bits 0x1800 are available /
define HEAP_KEYS_UPDATED 0x2000
/
tuple was updated and key cols* modified, or tuple deleted /
define HEAP_HOT_UPDATED 0x4000 /
tuple was HOT-updated /
define HEAP_ONLY_TUPLE 0x8000 /
this is heap-only tuple /
define HEAP2_XACT_MASK 0xE000 /
visibility-related bits */

postgres=# select * from heap_page_items(get_raw_page('tbl_hot', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |         t_data         
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------------------
  1 |   8152 |        1 |     34 |    554 |      0 |        0 | (0,1)  |           2 |       2306 |     24 |        |       | \x010000000d6c6f747475
  2 |   8112 |        1 |     34 |    554 |      0 |        0 | (0,2)  |           2 |       2306 |     24 |        |       | \x020000000d6c6f747475
  3 |   8072 |        1 |     34 |    554 |      0 |        0 | (0,3)  |           2 |       2306 |     24 |        |       | \x030000000d6c6f747475
  4 |   8032 |        1 |     34 |    554 |    555 |        0 | (0,5)  |       16386 |       1282 |     24 |        |       | \x040000000d6c6f747475
  5 |   8000 |        1 |     32 |    555 |      0 |        0 | (0,5)  |       32770 |      10498 |     24 |        |       | \x0400000009726178
(5 rows)

我們來(lái)理下:我們通過(guò)條件id=4挎袜;如何找到對(duì)應(yīng)的記錄

  1. 找到指向目標(biāo)數(shù)據(jù)tuple的索引tuple(0,4)
  2. 根據(jù)獲取索引tuple的位置(0,4);找到行指針lp為4的位置。即對(duì)應(yīng)的ctid為(0,5)
  3. 根據(jù)ctid為(0,5)盯仪;我們可以找到兩條tuple紊搪。根據(jù)PG的MVCC機(jī)制連判斷哪條tuple可見(jiàn)
  4. 可以找到對(duì)應(yīng)tuple

更新多次原理也差不多。

這個(gè)時(shí)候你會(huì)有一個(gè)疑問(wèn)“執(zhí)行vacuum全景;清理表tuple(0,4);少了步驟2耀石;那上面的流程就走不通了”。我們來(lái)解析下:

postgres=# vacuum tbl_hot;
VACUUM
postgres=# select lp, lp_off, lp_flags, t_ctid, t_infomask2 from heap_page_items(get_raw_page('tbl_hot', 0));
 lp | lp_off | lp_flags | t_ctid | t_infomask2 
----+--------+----------+--------+-------------
  1 |   8152 |        1 | (0,1)  |           2
  2 |   8112 |        1 | (0,2)  |           2
  3 |   8072 |        1 | (0,3)  |           2
  4 |      5 |        2 |        |            
  5 |   8040 |        1 | (0,5)  |       32770
(5 rows)

這時(shí)爸黄;為了解決這個(gè)問(wèn)題滞伟,postgresql會(huì)在合適的時(shí)候進(jìn)行行指針的重定向(redirect),這個(gè)過(guò)程稱為修剪。現(xiàn)在按照這種情況我們來(lái)理下:我們通過(guò)條件id=4炕贵;如何找到對(duì)應(yīng)的記錄

  1. 找到指向目標(biāo)數(shù)據(jù)tuple的索引tuple(0,4)
  2. 根據(jù)獲取索引tuple的位置(0,4)诗良;找到行指針lp為4的位置;這是lp_flags為2表示指針重定向lp為5鲁驶;即行指針對(duì)應(yīng)的位置是8040
  3. 通過(guò)指針可以找到對(duì)應(yīng)tuple鉴裹。

這是tuple(0,4);既然vacuum钥弯;表示可以再使用径荔;但是這是標(biāo)記是LP_REDIRECT;表明tuple非dead tuple脆霎;未進(jìn)行回收总处;不可以重復(fù)使用。這時(shí)你可能會(huì)有一個(gè)疑問(wèn)“那什么時(shí)候可以回收睛蛛?”鹦马;答案是這個(gè)tuple(0,4)不會(huì)標(biāo)記dead tuple。但是執(zhí)行vacuum;該page是可以回收空間忆肾;這個(gè)是PG的MVCC處理機(jī)制-vacuum的內(nèi)容荸频;可以分到下個(gè)篇幅再講。這里我們可以簡(jiǎn)單演示下:

postgres=# update tbl_hot set info = 'postgres' where id = 4;
UPDATE 1
postgres=# select lp, lp_off, lp_flags, t_ctid, t_infomask2 from heap_page_items(get_raw_page('tbl_hot', 0));
 lp | lp_off | lp_flags | t_ctid | t_infomask2 
----+--------+----------+--------+-------------
  1 |   8152 |        1 | (0,1)  |           2
  2 |   8112 |        1 | (0,2)  |           2
  3 |   8072 |        1 | (0,3)  |           2
  4 |      5 |        2 |        |            
  5 |   8040 |        1 | (0,6)  |       49154
  6 |   8000 |        1 | (0,6)  |       32770
(6 rows)
postgres=# vacuum tbl_hot;
VACUUM
postgres=# select lp, lp_off, lp_flags, t_ctid, t_infomask2 from heap_page_items(get_raw_page('tbl_hot', 0));
 lp | lp_off | lp_flags | t_ctid | t_infomask2 
----+--------+----------+--------+-------------
  1 |   8152 |        1 | (0,1)  |           2
  2 |   8112 |        1 | (0,2)  |           2
  3 |   8072 |        1 | (0,3)  |           2
  4 |      6 |        2 |        |            
  5 |      0 |        0 |        |            
  6 |   8032 |        1 | (0,6)  |       32770
(6 rows)
postgres=# select ctid,t.* from tbl_hot t;
 ctid  | id |   info   
-------+----+----------
 (0,1) |  1 | lottu
 (0,2) |  2 | lottu
 (0,3) |  3 | lottu
 (0,5) |  5 | lottu
 (0,6) |  4 | postgres
(5 rows)

最后客冈;當(dāng)更新的元祖是在其他page旭从;這是索引也會(huì)更新;這可以理解是行遷移场仲。這在oracle也是存在這種情況和悦。但是相比oracle更頻繁;當(dāng)然可以設(shè)置降低fillfactor渠缕;減少這種情況出現(xiàn)鸽素。

三、參考

https://blog.csdn.net/xiaohai928ww/article/details/98603707
https://www.postgresql.org/docs/12/pageinspect.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末亦鳞,一起剝皮案震驚了整個(gè)濱河市馍忽,隨后出現(xiàn)的幾起案子澜汤,更是在濱河造成了極大的恐慌,老刑警劉巖舵匾,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異谁不,居然都是意外死亡坐梯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門刹帕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吵血,“玉大人,你說(shuō)我怎么就攤上這事偷溺√8ǎ” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵挫掏,是天一觀的道長(zhǎng)侦另。 經(jīng)常有香客問(wèn)我,道長(zhǎng)尉共,這世上最難降的妖魔是什么褒傅? 我笑而不...
    開(kāi)封第一講書人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮袄友,結(jié)果婚禮上殿托,老公的妹妹穿的比我還像新娘。我一直安慰自己剧蚣,他們只是感情好支竹,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著鸠按,像睡著了一般礼搁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上目尖,一...
    開(kāi)封第一講書人閱讀 52,584評(píng)論 1 312
  • 那天叹坦,我揣著相機(jī)與錄音,去河邊找鬼卑雁。 笑死募书,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的测蹲。 我是一名探鬼主播莹捡,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扣甲!你這毒婦竟也來(lái)了篮赢?” 一聲冷哼從身側(cè)響起齿椅,我...
    開(kāi)封第一講書人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎启泣,沒(méi)想到半個(gè)月后涣脚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寥茫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年遣蚀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纱耻。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芭梯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弄喘,到底是詐尸還是另有隱情玖喘,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布蘑志,位于F島的核電站累奈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏急但。R本人自食惡果不足惜费尽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羊始。 院中可真熱鬧旱幼,春花似錦、人聲如沸突委。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)匀油。三九已至缘缚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間敌蚜,已是汗流浹背桥滨。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弛车,地道東北人齐媒。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像纷跛,于是被迫代替她去往敵國(guó)和親喻括。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361