PGSQL 的暫存表(temp table)跟 Oracle 不同:PGSQL 的暫存表是 DB Session 私用的;暫存表的生命週期只在 Session 活動期間,登出後就在資料庫裡完全消失。
因此有時候遇到稍微惱人的使用狀況,像是開了很大的暫存表,或是活的很久的暫存表(常駐 AP 咬住的 DB Session),有辦法找出 DB Session 對應的暫存表的話,就蠻有用處的~
- temp table 暫存表跟 temp file 暫存檔不同,暫存表相當於真實表格
- 暫存檔 temp file 是在 SQL 執行過程中產生(CTE materialization、Sorting、external Merge Disk 等發生時);而暫存表 temp table 是自己執行 CREATE TEMP TABLE 指令產生的表格物件
- 暫存檔的實體檔案,會放置在 $PGDATA/base/pgsql_temp/ 底下;而暫存表則待在普通表格之中(藏樹於林),不過檔名是以 t<NNN>_FFF 的格式(這部份敘述在 PGSQL 手冊的 Database File Layout 章節)
- 暫存表的 namespace(也就是 schema)主要是指定在 pg_temp_<NNN> 這樣的隱藏 schema 底下(有趣的是,這個資訊在手冊上著墨處不多,比較明確的是列在 search_path 這個 GUC 的說明頁面)
- 上述的 <NNN>,在 PGSQL 內部稱為 backend ID(這得要在幾份資料之間兜一下才會確認到的資訊),這跟 pg_stat_activity 會看得到的 PID 不一樣。
直接搜尋有關資料,比較明顯的內容是 PGSQL 的衍伸產品 GPDB 在 pg_stat_activity 有提供 sess_id 欄位呈現。
以及,從 OS 與 PID 的關係,有辦法透過 lsof 找出檔案對應的 open flie PID:Postgres 查找 Temp Table 建立者 - Postgres 筆記 - Ken
不過,要湊出純 PGSQL 手法的尋找方式,得作一點繞道。
- 首先,要先釐清 PGSQL 的 backend ID
- 要確認 backend ID 怎麼跟 PID 對上
- 透過 temp schema 命名方式,在 pg_class 或 pg_stat_user_tables 找出暫存表
- 把上面的東西 JOIN 在一起
找了一陣子,總算是找到 1 跟 2 的方式:這紀錄在兩個隱藏函數 pg_stat_get_backend_pid() 與 pg_stat_get_backend_idset():後者可以抓出當前 PGSQL 全部運作中的 backend ID,而前者可以從 backend ID 對應出 PID。
edb=# -- 這是 set-returning function,兩個前後順序有差
edb=# SELECT bkend_id, bkend_pid
edb-# FROM pg_stat_get_backend_idset() bkend_id,
edb-# pg_stat_get_backend_pid(bkend_id) bkend_pid;
bkend_id | bkend_pid
----------+-----------
1 | 11308
2 | 11310
3 | 11311
4 | 11380
5 | 11455
6 | 11306
7 | 11305
8 | 11307
(8 rows)
edb=#
通常對應 PID 最常使用的就是搭配 pg_stat_activity 一起看,這邊紀錄一版。
edb=# SELECT bkend_id, a.*
edb-# FROM pg_stat_get_backend_idset() bkend_id,
edb-# pg_stat_get_backend_pid(bkend_id) bkend_pid,
edb-# pg_stat_activity a
edb-# WHERE bkend_pid = a.pid
edb-# AND a.backend_type = 'client backend';
bkend_id | datid | datname | pid | leader_pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | xact_start | query_start | state_change | wait_event_type | wait_event | state | backend_xid | backend_xmin | query_id | query | backend_type
----------+-------+---------+-------+------------+----------+--------------+------------------+-------------+-----------------+-------------+----------------------------------+----------------------------------+----------------------------------+----------------------------------+-----------------+------------+--------+-------------+--------------+----------+----------------------------------------------------+----------------
4 | 15579 | edb | 11380 | | 10 | enterprisedb | psql | | | -1 | 26-SEP-22 14:32:25.759978 +00:00 | 26-SEP-22 14:33:31.818314 +00:00 | 26-SEP-22 14:33:31.818314 +00:00 | 26-SEP-22 14:33:31.818318 +00:00 | | | active | | 2200 | | SELECT bkend_id, a.* +| client backend
| | | | | | | | | | | | | | | | | | | | | FROM pg_stat_get_backend_idset() bkend_id, +|
| | | | | | | | | | | | | | | | | | | | | pg_stat_get_backend_pid(bkend_id) bkend_pid, +|
| | | | | | | | | | | | | | | | | | | | | pg_stat_activity a +|
| | | | | | | | | | | | | | | | | | | | | WHERE bkend_pid = a.pid +|
| | | | | | | | | | | | | | | | | | | | | AND a.backend_type = 'client backend'; |
5 | 15579 | edb | 11455 | | 10 | enterprisedb | psql | | | -1 | 26-SEP-22 14:32:54.571466 +00:00 | | | 26-SEP-22 14:32:54.577064 +00:00 | Client | ClientRead | idle | | | | | client backend
(2 rows)
edb=#
接著就是湊一下,就可以得到以下的 SQL
SELECT a.pid, bkend_id,
t.relnamespace::regnamespace, t.relname, t.relfilenode
FROM pg_stat_get_backend_idset() bkend_id,
pg_stat_get_backend_pid(bkend_id) bkend_pid,
pg_stat_activity a,
pg_class t
WHERE bkend_pid=a.pid
AND a.backend_type='client backend'
AND t.relnamespace::regnamespace::text = concat('pg_temp_', bkend_id);
作一點測試:建立 temp table 前/後,以及對照 temp table 的檔名。
edb=# -- 目前 Session 的 PID
edb=# SELECT pg_backend_pid();
pg_backend_pid
----------------
10814
(1 row)
edb=# -- 確認一下現在沒有暫存表
edb=# SELECT a.pid, bkend_id,
edb-# t.relnamespace::regnamespace, t.relname, t.relfilenode
edb-# FROM pg_stat_get_backend_idset() bkend_id,
edb-# pg_stat_get_backend_pid(bkend_id) bkend_pid,
edb-# pg_stat_activity a,
edb-# pg_class t
edb-# WHERE bkend_pid=a.pid
edb-# AND a.backend_type='client backend'
edb-# AND t.relnamespace::regnamespace::text = concat('pg_temp_', bkend_id);
pid | bkend_id | relnamespace | relname | relfilenode
-----+----------+--------------+---------+-------------
(0 rows)
edb=#
edb=# -- 開暫存表
edb=# create temp table xx(id int);
CREATE TABLE
edb=#
edb=# -- 再跑一次就看到了
edb=# SELECT a.pid, bkend_id,
t.relnamespace::regnamespace, t.relname, t.relfilenode
FROM pg_stat_get_backend_idset() bkend_id,
pg_stat_get_backend_pid(bkend_id) bkend_pid,
pg_stat_activity a,
pg_class t
WHERE bkend_pid=a.pid
AND a.backend_type='client backend'
AND t.relnamespace::regnamespace::text = concat('pg_temp_', bkend_id);
pid | bkend_id | relnamespace | relname | relfilenode
-------+----------+--------------+---------+-------------
10814 | 4 | pg_temp_4 | xx | 33518
(1 row)
edb=#
edb=# -- 檢視暫存表檔名
edb=# \! ls $PGDATA/base/15579/t*
/var/lib/edb/as14/data/base/15579/t4_33518
edb=#
完工~注意雖然上面只用相同 session 作檢查,但這個 SQL 只要 superuser,就都可以看其他 session 的狀況~
最後補充:
PGSQL 的 autovacuum 碰不到暫存表:也就是說,若有常駐的 DB session 並且會開著暫存表不斷使用的話,就要留意對暫存表的 VACUUM ANALYZE。
因此這邊的 SQL 可能就有點用處了~留意哪些暫存表變得太老,要調整對應的程式碼。
參考資料
Size of temp tables created in a particular session - Stack Overflow
Where is temporary table created? - Stack Overflow
Postgres 查找 Temp Table 建立者 - Postgres 筆記 - Ken
Temporary files in PostgreSQL – Steps to identify and fix temp file issues - Kloud DB
PostgreSQL temp files usage - DEV Community 👩💻👨💻
PostgreSQL Mailing List: Quirk of pg_temp schemas ...
PostgreSQL Mailing List: Thread: pg_temp schemas : Postgres Professional
pg_stat_get_backend_pid() - pgPedia - a PostgreSQL Encyclopedia
pg_stat_get_backend_idset() - pgPedia - a PostgreSQL Encyclopedia
有一種罕見的 Transaction ID wraparound 叫做 MxID wraparound,偷偷塞在這邊
PostgreSQL: Documentation: 14: 25.1. Routine Vacuuming - 25.1.5.1. Multixacts And Wraparound
PostgreSQL DBA(158) - Locks(tuple locks)_ITPUB博客
postgres/README.tuplock at master
沒有留言:
張貼留言