中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PostgreSQL Page頁結構解析(4)- 執行DML時表占用空間解析

發布時間:2020-08-06 23:48:29 來源:ITPUB博客 閱讀:169 作者:husthxd 欄目:關系型數據庫

本文介紹了在長事務(開啟事務,一直不提交/回滾)的情況下,通過使用pageinspect插件分析Update數據表導致數據表占用空間“暴漲”的原因。

一、測試場景

使用psql啟動會話Session B

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

testdb=# -- 創建表&插入100行數據
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=# -- 提交事務
testdb=# end;
COMMIT
testdb=# 

開啟新的Console創建,使用psql啟動會話Session A

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

testdb=# 
testdb=# -- do nothing

雖然什么都不做,但Session A仍然可以開啟一個事務,在這里這個事務一直不提交。
回到Session B,查看數據表t1的數據:

testdb=# --------------------------- Session B
testdb=# -- 查看數據表
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=# -- 查看數據占用空間
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)

再打開一個Shell窗口,使用pgbench持續不斷的更新t1,在此過程進行數據分析。

[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

二、數據分析

下面通過pageinspect插件分析t1數據頁中的數據。

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個數據頁的頭部數據和用戶數據
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)

可以看出,數據表占用空間一直在增長,已遠遠超出一個數據頁的范圍。同時,我們注意到t_xmax、t_infomask2、t_infomask中的部分值與先前首次插入的數據的取值不同。
t_xmax
該值 > 0,表示該行數據已廢棄,該值為delete/update操作的事務號
t_infomask2
該值為16386,轉換為十六進制顯示:

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

前(低)11位表示屬性個數,值為2,也就是說數據表有2個屬性(字段);\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,轉換為十六進制顯示:

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

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

三、空間回收

數據表t1不管如何Update,實際的數據行數只有100行,大小遠不到8K,但為何占用了幾百KB的空間?原因是PG為了MVCC(多版本并發控制)的需要保留了更新前的“垃圾”數據,這些垃圾數據可以通過vacuum機制定期清理這些垃圾數據。但在本例中,由于“長”事務的存在,垃圾數據不能正常清理。

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均不能正常回收垃圾數據,原因是PG認為這些垃圾數據對于正在活動中的事務(Session A)是可見的。
我們回顧一下,Session A的事務號:1600324,Session B插入數據時的事務號:1600322,更新數據時的事務號 > 1600324,Session A(活動事務)查詢t1時,通過PG的snapshot機制實現“一致性”讀。PG的snapshot可以通過txid_current_snapshot函數獲得:

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. 未提交并活躍的事務中最小的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.所有已提交事務中最大的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.

數據行中的xin和xmax符合條件xmax>活動事務號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)

反之,活動事務提交后,垃圾數據占用的空間可正常回收:

testdb=# --------------------------- Session A
testdb=# -- 結束事務
testdb=# end;
COMMIT

執行vacuum命令回收垃圾數據:

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命令還不能回收數據?為什么?請注意t_infomask2標志HEAP_HOT_UPDATED,簡單來說,在update chain中的data不會回收,由于涉及到HOT機制,詳細后續再解析。

四、小結

主要有三點需要總結:
1、保留原數據:PG沒有回滾段,在執行更新/刪除操作時并沒有真正的更新和刪除,而是保留原有數據,在合適的時候通過vacuum機制清理垃圾數據;
2、避免長事務:為了避免垃圾數據暴漲,在業務邏輯允許的情況下應盡可能的盡快提交事務,避免長事務的出現;
3、查詢操作:使用JDBC驅動或者其他驅動連接PG,如明確知道只執行查詢操作,請開啟自動提交事務。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

江阴市| 宁强县| 无棣县| 尼玛县| 甘泉县| 深水埗区| 精河县| 康马县| 茂名市| 建宁县| 高阳县| 炉霍县| 伊春市| 雅安市| 故城县| 达拉特旗| 远安县| 宽城| 阿拉善盟| 若尔盖县| 成安县| 苏州市| 五常市| 梅州市| 新兴县| 石门县| 高要市| 武城县| 北碚区| 太康县| 平顶山市| 宿松县| 海宁市| 阳泉市| 扎兰屯市| 板桥市| 浦县| 武乡县| 四川省| 肇东市| 盐津县|