PostgreSQL Page頁結(jié)構(gòu)解析(4)- 執(zhí)行DML時(shí)表占用空間解析

本文介紹了在長(zhǎng)事務(wù)(開啟事務(wù)贝室,一直不提交/回滾)的情況下沾鳄,通過使用pageinspect插件分析Update數(shù)據(jù)表導(dǎo)致數(shù)據(jù)表占用空間“暴漲”的原因诸蚕。

一舀射、測(cè)試場(chǎng)景

使用psql啟動(dòng)會(huì)話Session B

testdb=# --------------------------- Session B
testdb=# -- 開啟事務(wù)
testdb=# begin;
BEGIN
testdb=# 
testdb=# select txid_current();  
 txid_current 
--------------
      1600322
(1 row)

testdb=# -- 創(chuàng)建表&插入100行數(shù)據(jù)
testdb=# drop table if exists t1;
DROP TABLE
testdb=# create table t1(id int,c1 varchar(50));
CREATE TABLE
testdb=# insert into t1 select generate_series(1,100),'#abcd#';
INSERT 0 100
testdb=# select txid_current();  
 txid_current 
--------------
      1600322
(1 row)

testdb=# select count(*) from t1;
 count 
-------
   100
(1 row)

testdb=# 
testdb=# -- 提交事務(wù)
testdb=# end;
COMMIT
testdb=# 

開啟新的Console創(chuàng)建冲泥,使用psql啟動(dòng)會(huì)話Session A

testdb=# --------------------------- Session A
testdb=# -- 開啟事務(wù)
testdb=# begin;
BEGIN
testdb=# 
testdb=# -- 查詢當(dāng)前事務(wù)
testdb=# select txid_current();  
 txid_current 
--------------
      1600324
(1 row)

testdb=# 
testdb=# -- do nothing

雖然什么都不做驹碍,但Session A仍然可以開啟一個(gè)事務(wù),在這里這個(gè)事務(wù)一直不提交凡恍。
回到Session B志秃,查看數(shù)據(jù)表t1的數(shù)據(jù):

testdb=# --------------------------- Session B
testdb=# -- 查看數(shù)據(jù)表
testdb=# select ctid, xmin, xmax, cmin, cmax,id from t1 limit 8;
 ctid  |  xmin   | xmax | cmin | cmax | id 
-------+---------+------+------+------+----
 (0,1) | 1600322 |    0 |    4 |    4 |  1
 (0,2) | 1600322 |    0 |    4 |    4 |  2
 (0,3) | 1600322 |    0 |    4 |    4 |  3
 (0,4) | 1600322 |    0 |    4 |    4 |  4
 (0,5) | 1600322 |    0 |    4 |    4 |  5
 (0,6) | 1600322 |    0 |    4 |    4 |  6
 (0,7) | 1600322 |    0 |    4 |    4 |  7
 (0,8) | 1600322 |    0 |    4 |    4 |  8
(8 rows)

testdb=# -- 查看數(shù)據(jù)占用空間
testdb=# \set v_tablename t1
testdb=# SELECT pg_size_pretty( pg_total_relation_size(:'v_tablename') );
 pg_size_pretty 
----------------
 8192 bytes
(1 row)

testdb=# -- page_header
testdb=# SELECT * FROM page_header(get_raw_page('t1', 0));
    lsn     | checksum | flags | lower | upper | special | pagesize | version | prune_xid 
------------+----------+-------+-------+-------+---------+----------+---------+-----------
 1/4476E4A0 |        0 |     0 |   424 |  4192 |    8192 |     8192 |       4 |         0
(1 row)

再打開一個(gè)Shell窗口,使用pgbench持續(xù)不斷的更新t1嚼酝,在此過程進(jìn)行數(shù)據(jù)分析浮还。

[xdb@localhost benchmark]$ cat update.sql 
\set rowid random(1,100)
begin;
update t1 set c1=:rowid where id= :rowid;
end;

[xdb@localhost benchmark]$ pgbench -c 2 -C -f ./update.sql -j 1 -n -T 600 -U xdb testdb

二、數(shù)據(jù)分析

下面通過pageinspect插件分析t1數(shù)據(jù)頁中的數(shù)據(jù)闽巩。

testdb=# \set v_tablename t1
testdb=# 
testdb=# SELECT pg_size_pretty( pg_total_relation_size(:'v_tablename') );
 pg_size_pretty 
----------------
 160 kB
(1 row)

testdb=# -- 查看第0個(gè)數(shù)據(jù)頁的頭部數(shù)據(jù)和用戶數(shù)據(jù)
testdb=# SELECT * FROM page_header(get_raw_page('t1', 0));
    lsn     | checksum | flags | lower | upper | special | pagesize | version | prune_xid 
------------+----------+-------+-------+-------+---------+----------+---------+-----------
 1/44787990 |        0 |     2 |   840 |   864 |    8192 |     8192 |       4 |   1600325
(1 row)

testdb=# select * from heap_page_items(get_raw_page('t1',0)) limit 10;
 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 |     35 | 1600322 | 1600365 |        0 | (0,141) |       16386 |       1282 |     24 |        |       | \x010000000f236162636423
  2 |   8112 |        1 |     35 | 1600322 | 1600325 |        0 | (0,101) |       16386 |       1282 |     24 |        |       | \x020000000f236162636423
  3 |   8072 |        1 |     35 | 1600322 | 1600421 |        0 | (0,197) |       16386 |       1282 |     24 |        |       | \x030000000f236162636423
  4 |   8032 |        1 |     35 | 1600322 | 1600435 |        0 | (1,7)   |           2 |       1282 |     24 |        |       | \x040000000f236162636423
  5 |   7992 |        1 |     35 | 1600322 | 1600474 |        0 | (1,46)  |           2 |       1282 |     24 |        |       | \x050000000f236162636423
  6 |   7952 |        1 |     35 | 1600322 | 1600538 |        0 | (1,110) |           2 |       1282 |     24 |        |       | \x060000000f236162636423
  7 |   7912 |        1 |     35 | 1600322 | 1600396 |        0 | (0,172) |       16386 |       1282 |     24 |        |       | \x070000000f236162636423
  8 |   7872 |        1 |     35 | 1600322 | 1600331 |        0 | (0,107) |       16386 |       1282 |     24 |        |       | \x080000000f236162636423
  9 |   7832 |        1 |     35 | 1600322 | 1600531 |        0 | (1,103) |           2 |       1282 |     24 |        |       | \x090000000f236162636423
 10 |   7792 |        1 |     35 | 1600322 | 1600413 |        0 | (0,189) |       16386 |       1282 |     24 |        |       | \x0a0000000f236162636423
(10 rows)

testdb=# -- 再次查看空間占用
testdb=# SELECT pg_size_pretty( pg_total_relation_size(:'v_tablename') );
 pg_size_pretty 
----------------
 360 kB
(1 row)

可以看出钧舌,數(shù)據(jù)表占用空間一直在增長(zhǎng),已遠(yuǎn)遠(yuǎn)超出一個(gè)數(shù)據(jù)頁的范圍涎跨。同時(shí)洼冻,我們注意到t_xmax、t_infomask2隅很、t_infomask中的部分值與先前首次插入的數(shù)據(jù)的取值不同撞牢。
t_xmax
該值 > 0,表示該行數(shù)據(jù)已廢棄,該值為delete/update操作的事務(wù)號(hào)
t_infomask2
該值為16386屋彪,轉(zhuǎn)換為十六進(jìn)制顯示:

[xdb@localhost benchmark]$ echo "obase=16;16386"|bc
4002

前(低)11位表示屬性個(gè)數(shù)所宰,值為2,也就是說數(shù)據(jù)表有2個(gè)屬性(字段)畜挥;\x4000表示HEAP_HOT_UPDATED歧匈,官方解釋如下:

An updated tuple, for which the next tuple in the chain is a heap-only tuple. Marked with HEAP_HOT_UPDATED flag.

t_infomask
該值為1282,轉(zhuǎn)換為十六進(jìn)制顯示:

[xdb@localhost benchmark]$ echo "obase=16;1282"|bc
502

\0x0502 = HEAP_XMIN_COMMITTED | HEAP_XMAX_COMMITTED
意思是插入數(shù)據(jù)的事務(wù)和更新(或刪除)的事務(wù)均已提交砰嘁。

三、空間回收

數(shù)據(jù)表t1不管如何Update勘究,實(shí)際的數(shù)據(jù)行數(shù)只有100行矮湘,大小遠(yuǎn)不到8K,但為何占用了幾百KB的空間口糕?原因是PG為了MVCC(多版本并發(fā)控制)的需要保留了更新前的“垃圾”數(shù)據(jù)缅阳,這些垃圾數(shù)據(jù)可以通過vacuum機(jī)制定期清理這些垃圾數(shù)據(jù)。但在本例中景描,由于“長(zhǎng)”事務(wù)的存在十办,垃圾數(shù)據(jù)不能正常清理。

testdb=# SELECT pg_size_pretty( pg_total_relation_size(:'v_tablename') );
 pg_size_pretty 
----------------
 472 kB
(1 row)

testdb=# vacuum t1;
VACUUM
testdb=# SELECT pg_size_pretty( pg_total_relation_size(:'v_tablename') );
 pg_size_pretty 
----------------
 472 kB
(1 row)

testdb=# vacuum full;
VACUUM
testdb=# SELECT pg_size_pretty( pg_total_relation_size(:'v_tablename') );
 pg_size_pretty 
----------------
 472 kB
(1 row)

使用命令vacuum t1和vacuum full均不能正吵祝回收垃圾數(shù)據(jù)向族,原因是PG認(rèn)為這些垃圾數(shù)據(jù)對(duì)于正在活動(dòng)中的事務(wù)(Session A)是可見的。
我們回顧一下棠绘,Session A的事務(wù)號(hào):1600324件相,Session B插入數(shù)據(jù)時(shí)的事務(wù)號(hào):1600322,更新數(shù)據(jù)時(shí)的事務(wù)號(hào) > 1600324氧苍,Session A(活動(dòng)事務(wù))查詢t1時(shí)夜矗,通過PG的snapshot機(jī)制實(shí)現(xiàn)“一致性”讀。PG的snapshot可以通過txid_current_snapshot函數(shù)獲得:

testdb=# select txid_current_snapshot();
  txid_current_snapshot  
-------------------------
 1600324:1612465:1600324
(1 row)

返回值分為三部分让虐,分別是xin紊撕、xmax和xip_list:

格式:xin:xmax:xip_list
xin:Earliest transaction ID (txid) that is still active. 未提交并活躍的事務(wù)中最小的XID
xmax:First as-yet-unassigned txid. All txids greater than or equal to this are not yet started as of the time of the snapshot, and thus invisible.所有已提交事務(wù)中最大的XID + 1
xip_list:Active txids at the time of the snapshot. All of them are between xmin and xmax. A txid that is xmin <= txid < xmax and not in this list was already completed at the time of the snapshot, and thus either visible or dead according to its commit status.

數(shù)據(jù)行中的xin和xmax符合條件xmax>活動(dòng)事務(wù)號(hào)xid(1600324)>xin的所有記錄均不能被回收!

testdb=# --------------------------- Session B
testdb=# vacuum t1;
VACUUM
testdb=# SELECT pg_size_pretty( pg_total_relation_size(:'v_tablename') );
 pg_size_pretty 
----------------
 472 kB
(1 row)

testdb=# vacuum full;
VACUUM
testdb=# SELECT pg_size_pretty( pg_total_relation_size(:'v_tablename') );
 pg_size_pretty 
----------------
 472 kB
(1 row)

反之赡突,活動(dòng)事務(wù)提交后对扶,垃圾數(shù)據(jù)占用的空間可正常回收:

testdb=# --------------------------- Session A
testdb=# -- 結(jié)束事務(wù)
testdb=# end;
COMMIT

執(zhí)行vacuum命令回收垃圾數(shù)據(jù):

testdb=# --------------------------- Session B
testdb=# vacuum t1;
VACUUM
testdb=# SELECT pg_size_pretty( pg_total_relation_size(:'v_tablename') );
 pg_size_pretty 
----------------
 472 kB
(1 row)
testdb=# vacuum full;

VACUUM
testdb=# SELECT pg_size_pretty( pg_total_relation_size(:'v_tablename') );
 pg_size_pretty 
----------------
 8192 bytes
(1 row)

通過vacuum t1命令還不能回收數(shù)據(jù)惭缰?為什么辩稽?請(qǐng)注意t_infomask2標(biāo)志HEAP_HOT_UPDATED,簡(jiǎn)單來說从媚,在update chain中的data不會(huì)回收逞泄,由于涉及到HOT機(jī)制,詳細(xì)后續(xù)再解析。

四喷众、小結(jié)

主要有三點(diǎn)需要總結(jié):
1各谚、保留原數(shù)據(jù):PG沒有回滾段,在執(zhí)行更新/刪除操作時(shí)并沒有真正的更新和刪除到千,而是保留原有數(shù)據(jù)昌渤,在合適的時(shí)候通過vacuum機(jī)制清理垃圾數(shù)據(jù);
2憔四、避免長(zhǎng)事務(wù):為了避免垃圾數(shù)據(jù)暴漲膀息,在業(yè)務(wù)邏輯允許的情況下應(yīng)盡可能的盡快提交事務(wù),避免長(zhǎng)事務(wù)的出現(xiàn)了赵;
3潜支、查詢操作:使用JDBC驅(qū)動(dòng)或者其他驅(qū)動(dòng)連接PG,如明確知道只執(zhí)行查詢操作柿汛,請(qǐng)開啟自動(dòng)提交事務(wù)冗酿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市络断,隨后出現(xiàn)的幾起案子裁替,更是在濱河造成了極大的恐慌,老刑警劉巖貌笨,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弱判,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡锥惋,警方通過查閱死者的電腦和手機(jī)裕循,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來净刮,“玉大人剥哑,你說我怎么就攤上這事⊙透福” “怎么了株婴?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)暑认。 經(jīng)常有香客問我困介,道長(zhǎng),這世上最難降的妖魔是什么蘸际? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任座哩,我火速辦了婚禮,結(jié)果婚禮上粮彤,老公的妹妹穿的比我還像新娘姜骡。我一直安慰自己,他們只是感情好屿良,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尘惧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪喷橙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天贰逾,我揣著相機(jī)與錄音,去河邊找鬼似踱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛稽煤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播酵熙,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轧简,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼匾二!你這毒婦竟也來了哮独?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤察藐,失蹤者是張志新(化名)和其女友劉穎皮璧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體分飞,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悴务,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了譬猫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讯檐。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖染服,靈堂內(nèi)的尸體忽然破棺而出别洪,到底是詐尸還是另有隱情,我是刑警寧澤柳刮,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布挖垛,位于F島的核電站痒钝,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏晕换。R本人自食惡果不足惜午乓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闸准。 院中可真熱鬧益愈,春花似錦、人聲如沸夷家。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽库快。三九已至摸袁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間义屏,已是汗流浹背靠汁。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闽铐,地道東北人蝶怔。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像兄墅,于是被迫代替她去往敵國(guó)和親踢星。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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