1. 程式人生 > 其它 >postgresql-磁碟空間不足問題排查

postgresql-磁碟空間不足問題排查

問題背景

加壓測試過程中發現插入資料過程中報錯:could not write to hash-join temporary file: 裝置上沒有空間。但是檢視伺服器還有很多空閒空間,是什麼導致這樣的錯誤呢?

檢視執行指令碼

insert into db_zjgj.result_rule_cwjbxx_db_sacw_t_cw_cwjbxx 
select db_zjgj.uuid(),c_bh,'2E810338E4F2CEE0462E9A021A0E0816','財物-財物處置資訊中,非先行處置類的處置資訊,處置日期不能小於裁判生效日期','7B7DCB103239F5CBAB4106016DE258D1'
from db_zjgj.temp_cwjbxx_db_sacw_t_cw_cwjbxx where EXISTS (
SELECT
1
FROM
db_zjgj.temp_cwczxx_db_sacw_t_cw_cwczxx
WHERE
db_zjgj.temp_cwczxx_db_sacw_t_cw_cwczxx.c_cwbh = db_zjgj.temp_cwjbxx_db_sacw_t_cw_cwjbxx.c_bh
AND db_zjgj.temp_cwczxx_db_sacw_t_cw_cwczxx.d_czsj IS NOT NULL
)
AND EXISTS (
SELECT
1
FROM
db_zjgj.temp_ajjbxx_db_sacw_t_aj_ajjbxx AS ajjbxx
WHERE
db_zjgj.temp_cwjbxx_db_sacw_t_cw_cwjbxx.c_ajbh = ajjbxx.c_bh
AND ajjbxx.d_pjsxrq IS NOT NULL
)
AND EXISTS (
SELECT
1
FROM
db_zjgj.temp_ajjbxx_db_sacw_t_aj_ajjbxx AS ajjbxx,
db_zjgj.temp_cwczxx_db_sacw_t_cw_cwczxx
WHERE
db_zjgj.temp_cwjbxx_db_sacw_t_cw_cwjbxx.c_ajbh = ajjbxx.c_bh
AND db_zjgj.temp_cwjbxx_db_sacw_t_cw_cwjbxx.c_bh = db_zjgj.temp_cwczxx_db_sacw_t_cw_cwczxx.c_cwbh
AND db_zjgj.temp_cwczxx_db_sacw_t_cw_cwczxx.d_czsj < ajjbxx.d_pjsxrq
)
--檢視執行計劃
Hash Semi Join (cost=270531577.85..324240042.87 rows=113055 width=33)
Hash Cond: ((temp_cwjbxx_db_sacw_t_cw_cwjbxx.c_ajbh)::text = (ajjbxx.c_bh)::text)
-> Hash Semi Join (cost=270527939.60..324202660.37 rows=113055 width=99)
Hash Cond: (((temp_cwczxx_db_sacw_t_cw_cwczxx.c_cwbh)::text = (temp_cwczxx_db_sacw_t_cw_cwczxx_1.c_cwbh)::text) AND ((temp_cwjbxx_db_sacw_t_cw_cwjbxx.c_ajbh)::text = (ajjbxx_1.c_bh)::text))
-> Hash Semi Join (cost=10073.78..43895.94 rows=225857 width=99)
Hash Cond: ((temp_cwjbxx_db_sacw_t_cw_cwjbxx.c_bh)::text = (temp_cwczxx_db_sacw_t_cw_cwczxx.c_cwbh)::text)
-> Seq Scan on temp_cwjbxx_db_sacw_t_cw_cwjbxx (cost=0.00..17784.08 rows=451208 width=66)
-> Hash (cost=5485.57..5485.57 rows=225857 width=33)
-> Seq Scan on temp_cwczxx_db_sacw_t_cw_cwczxx (cost=0.00..5485.57 rows=225857 width=33)
Filter: (d_czsj IS NOT NULL)
-> Hash (cost=169739766.32..169739766.32 rows=3771811900 width=66)
-> Nested Loop (cost=0.00..169739766.32 rows=3771811900 width=66)
Join Filter: (temp_cwczxx_db_sacw_t_cw_cwczxx_1.d_czsj < ajjbxx_1.d_pjsxrq)
-> Seq Scan on temp_cwczxx_db_sacw_t_cw_cwczxx temp_cwczxx_db_sacw_t_cw_cwczxx_1 (cost=0.00..5485.57 rows=225857 width=41)
-> Materialize (cost=0.00..2870.50 rows=50100 width=41)
-> Seq Scan on temp_ajjbxx_db_sacw_t_aj_ajjbxx ajjbxx_1 (cost=0.00..2620.00 rows=50100 width=41)
-> Hash (cost=2620.00..2620.00 rows=50100 width=33) -> Seq Scan on temp_ajjbxx_db_sacw_t_aj_ajjbxx ajjbxx (cost=0.00..2620.00 rows=50100 width=33)

通過執行計劃發現表連線使用了全表掃描以及nested loop連線。

查看錶資料量和索引情況

--檢視個表資料量
db_zjgj.temp_ajjbxx_db_sacw_t_aj_ajjbxx:50100
db_zjgj.temp_cwczxx_db_sacw_t_cw_cwczxx:225857
db_zjgj.temp_cwjbxx_db_sacw_t_cw_cwjbxx:451208

發現各表均無索引,且沒有主鍵。(瞭解到這些表都是抽數過程中生成的,在抽數完成後均會刪除)。

優化

--新增主鍵和邏輯外來鍵索引
alter table db_zjgj.temp_ajjbxx_db_sacw_t_aj_ajjbxx add primary key(c_bh);
create index i_ajjbxx_d_pjsxrq on db_zjgj.temp_ajjbxx_db_sacw_t_aj_ajjbxx(d_pjsxrq);
alter table db_zjgj.temp_cwczxx_db_sacw_t_cw_cwczxx add primary key(c_bh);
create index i_cwczxx_c_cwbh on db_zjgj.temp_cwczxx_db_sacw_t_cw_cwczxx(c_cwbh);
create index i_cwczxx_d_czsj on db_zjgj.temp_cwczxx_db_sacw_t_cw_cwczxx(d_czsj);
alter table db_zjgj.temp_cwjbxx_db_sacw_t_cw_cwjbxx add primary key(c_bh);
create index i_cwjbxx_c_ajbh on db_zjgj.temp_cwjbxx_db_sacw_t_cw_cwjbxx(c_ajbh);
--檢視執行計劃
Hash Semi Join (cost=13712.87..298118.93 rows=113055 width=33)
Hash Cond: ((temp_cwjbxx_db_sacw_t_cw_cwjbxx.c_ajbh)::text = (ajjbxx.c_bh)::text)
-> Nested Loop Semi Join (cost=10074.62..260736.42 rows=113055 width=99)
Join Filter: ((temp_cwczxx_db_sacw_t_cw_cwczxx.c_cwbh)::text = (temp_cwczxx_db_sacw_t_cw_cwczxx_1.c_cwbh)::text)
-> Hash Semi Join (cost=10073.78..43895.94 rows=225857 width=99)
Hash Cond: ((temp_cwjbxx_db_sacw_t_cw_cwjbxx.c_bh)::text = (temp_cwczxx_db_sacw_t_cw_cwczxx.c_cwbh)::text)
-> Seq Scan on temp_cwjbxx_db_sacw_t_cw_cwjbxx (cost=0.00..17784.08 rows=451208 width=66)
-> Hash (cost=5485.57..5485.57 rows=225857 width=33)
-> Seq Scan on temp_cwczxx_db_sacw_t_cw_cwczxx (cost=0.00..5485.57 rows=225857 width=33)
Filter: (d_czsj IS NOT NULL)
-> Nested Loop (cost=0.83..0.95 rows=1 width=66)
Join Filter: (temp_cwczxx_db_sacw_t_cw_cwczxx_1.d_czsj < ajjbxx_1.d_pjsxrq)
-> Index Scan using temp_ajjbxx_db_sacw_t_aj_ajjbxx_pkey on temp_ajjbxx_db_sacw_t_aj_ajjbxx ajjbxx_1 (cost=0.41..0.45 rows=1 width=41)
Index Cond: ((c_bh)::text = (temp_cwjbxx_db_sacw_t_cw_cwjbxx.c_ajbh)::text)
-> Index Scan using i_cwczxx_c_cwbh on temp_cwczxx_db_sacw_t_cw_cwczxx temp_cwczxx_db_sacw_t_cw_cwczxx_1 (cost=0.42..0.48 rows=1 width=41)
Index Cond: ((c_cwbh)::text = (temp_cwjbxx_db_sacw_t_cw_cwjbxx.c_bh)::text)
-> Hash (cost=2620.00..2620.00 rows=50100 width=33)
-> Seq Scan on temp_ajjbxx_db_sacw_t_aj_ajjbxx ajjbxx (cost=0.00..2620.00 rows=50100 width=33)
Filter: (d_pjsxrq IS NOT NULL)

新增索引後cost降了下來,資料能夠順利插入,最終sql大概需要6s左右。

疑問

為什麼檢視伺服器還有空閒空間,但是執行sql卻報錯磁碟空間不足呢。

--檢視abase資料檔案目錄:/opt/thunisoft/abdata/3.6/abase1/base/pgsql_tmp
[thunisoft@localhost base]$ du -sh *|sort
19M pgsql_tmp
3.2G 408143
6.3G 410629
7.0M 13236
7.1M 1
7.2M 13241
7.2M 16444
--臨時目錄下面有許多個臨時檔案
[thunisoft@localhost pgsql_tmp]$ ls |wc -w
65551
--臨時檔案均已pg_sql_tmp23277開頭
[thunisoft@localhost pgsql_tmp]$ ll
...
-rw-------. 1 thunisoft thunisoft 0 Aug 27 14:24 pgsql_tmp23277.9998
-rw-------. 1 thunisoft thunisoft 0 Aug 27 14:24 pgsql_tmp23277.9999
...

--23277標識程序號,從pg_log日誌檔案中可以找到該程序為當前正在執行sql

可以看出臨時目錄下面有許多檔案,大小為0,pgsql_tmp所佔用的空間為19M。

場景還原

--磁碟空間使用情況
[thunisoft@localhost base]$ df -lh
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root 18G 12G 4.6G 73% /
tmpfs 5.9G 4.8K 5.9G 1% /dev/shm
/dev/sda1 485M 33M 427M 8% /boot

--刪除剛剛所建的索引,重新執行該sql,發現pgsql_tmp會不斷地增大
[thunisoft@localhost base]$ du -sh *|sort
1.6G pgsql_tmp
3.2G 408143
6.3G 410629
7.0M 13236
7.1M 1
7.2M 13241
7.2M 16444
--最終直至佔滿所有空間
[thunisoft@localhost base]$ df -lh
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root 18G 17G 254M 99% /
tmpfs 5.9G 4.0K 5.9G 1% /dev/shm
/dev/sda1 485M 33M 427M 8% /boot
--空間佔滿 報錯後這些臨時檔案空間大部分被回收,但是檔案還在,檔案個數仍為65551
[thunisoft@localhost base]$ du -sh *|sort
19M pgsql_tmp
...
[thunisoft@localhost pgsql_tmp]$ ls |wc -w
65551

可以看出該sql執行時臨時檔案會不斷的增大,直至佔滿空間報錯,當sql報錯後臨時檔案大部分被清空,磁碟空間又將得到釋放,所以開始看到的磁碟空間並沒有滿,但是報錯卻是磁碟空間滿了。

那些情況會生成這些臨時檔案

據瞭解查詢要使用的記憶體超出work_mem的大小時(包括排序,DISTINCT,MERGE JOIN,HASH JOIN,笛卡爾積,雜湊聚合,分組聚合,遞迴查詢)等操作時會使用臨時檔案來儲存中間過程的資料。如果頻繁的進行上述操作,臨時檔案將會快速增長。只有重啟能夠解決該問題,重啟後將清空所有臨時檔案。

--查詢使用臨時檔案相關
--1.每個程序臨時檔案空間的限制,如果超過改值,查詢將取消,預設無限制
#temp_file_limit = -1 # limits per-process temp file space
# in kB, or -1 for no limit
--2.當臨時檔案使用量大於設定閾值時,記錄日誌,預設不記錄
#log_temp_files = -1 # log temporary files equal or larger
# than the specified size in kilobytes;
# -1 disables, 0 logs all temp files
--3.當超過work_mem時使用臨時檔案
#work_mem (integer)

結語

1.回到最開始的sql,這些臨時表可以在資料插入表後建立索引,然後再執行最後的抽數,這樣效率會高一點,巢狀迴圈耗費cpu,磁碟io,以及臨時檔案佔用高

2.abase為了提高執行效率一些操作會使用記憶體代替臨時儲存,當記憶體不足時就會使用臨時檔案儲存中間資料。

3.可酌情設定temp_file_limit 為磁碟空間的10%,當臨時檔案佔用磁碟過高,自動取消該查詢,記錄查詢語句

4.一般查詢如果耗費大量的臨時檔案,有可能是沒有索引導致