摘要:臨時表作為一個SQL標准中的表類型,各個廠商在實現時,往往卻不相同,甚至行為上也存在差異,本文小結下GaussDB(DWS)的臨時表使用場景。
本文分享自華為雲社區《GaussDB(DWS)臨時表小結》,作者: sincatter 。
語法介紹
如下為創建表的基本語法(詳見手冊):
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name ({ column_name data_type [ compress_mode ] [ COLLATE collation ] [ column_constraint [ ... ] ] | table_constraint | LIKE source_table [ like_option [...] ] } [, ... ]) [ WITH ( {storage_parameter = value} [, ... ] ) ] [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ COMPRESS | NOCOMPRESS ] [ TABLESPACE tablespace_name ] [ DISTRIBUTE BY { REPLICATION | { HASH ( column_name [,...] ) } } ] [ TO { GROUP groupname | NODE ( nodename [, ... ] ) } ];
其中臨時表相關的關鍵字有:
- [ GLOBAL | LOCAL ]
創建臨時表時可以在TEMP或TEMPORARY前指定GLOBAL或LOCAL關鍵字。目前GaussDB(DWS)設立這兩個關鍵字,僅僅是為了兼容SQL標准,實際行為上無論指定的是GLOBAL還是LOCAL,GaussDB(DWS)都只會創建為本地臨時表,即只有LOCAL關鍵字是有效的。
- { TEMPORARY | TEMP }
TEMP和TEMPORARY等價。如果指定TEMP或TEMPORARY關鍵字,則創建的表為臨時表。臨時表行為上的主要特征為只在當前會話可見,本會話結束后會自動刪除。由於臨時表只在當前會話創建,對於涉及對臨時表操作的DDL語句,很容易產生DDL失敗的報錯。因此,建議DDL語句中不要對臨時表進行操作。
- ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP }
ON COMMIT選項決定在事務中執行創建臨時表操作,當事務提交時,此臨時表的后續操作。有以下三個選項:
- PRESERVE ROWS(缺省值):提交時不對臨時表做任何操作,臨時表及其表數據保持不變。
- DELETE ROWS:提交時刪除臨時表中數據。
- DROP:提交時刪除此臨時表。
原理介紹
GaussDB(DWS)的臨時表機制繼承自PostgreSQL,臨時表在元數據和數據存儲上與普通表基本無差異,具體來說,臨時表是通過建表時將其Schema指定為與session id相關的一個schema,其他session實際上也是可以在系統表中查看到當前臨時表的元數據。GaussDB(DWS)會利用schema進行臨時表的不同session間隔離。這里通過兩個現象去說明這個機制:
現象一:
一個session_1創建一個臨時表:
postgres=# create temp table tt1(a int); CREATE TABLE postgres=# \d List of relations Schema | Name | Type | Owner | Storage ------------------------------------------+------+-------+-------+---------------------------------- pg_temp_coordinator1_2_4_139820525504256 | tt1 | table | xucw | {orientation=row,compression=no} (1 row) postgres=# select relname,relnamespace from pg_class where relname like 'tt%'; relname | relnamespace ---------+-------------- tt1 | 24600 (1 row)
另外一個session_2,可以一樣可以通過pg_class查看臨時表的表結構:
postgres=# select relname,relnamespace from pg_class where relname like 'tt%'; relname | relnamespace ---------+-------------- tt1 | 24600 (1 row)
但session_2中是無法查看當前臨時表中的數據:
postgres=# select * from pg_temp_coordinator1_2_4_139820525504256.tt1; ERROR: Can only access temp objects of the current session. LINE 1: select * from pg_temp_coordinator1_2_4_139820525504256.tt1;
現象二:
創建一個臨時表后,再根據這個臨時表的schema,去創建一個相同schema的普通表:
postgres=# create temp table tt1(a int); CREATE TABLE postgres=# \d List of relations Schema | Name | Type | Owner | Storage ------------------------------------------+------+-------+-------+---------------------------------- pg_temp_coordinator1_2_3_139820525504256 | tt1 | table | xucw | {orientation=row,compression=no} (1 row) postgres=# create table pg_temp_coordinator1_2_3_139820525504256.tt2(a int); CREATE TABLE postgres=# select relname,relnamespace from pg_class where relname like 'tt%'; relname | relnamespace ---------+-------------- tt1 | 24592 tt2 | 24592 (2 rows)
隨后,退出當前session,重新連接查看表狀態,我們會神奇發現之前創建的臨時表tt1消失的同時,創建的普通表tt2也一樣消失了。
postgres=# select relname,relnamespace from pg_class where relname like 'tt%'; relname | relnamespace ---------+-------------- (0 rows)
使用注意
- 與使用永久表相比,使用臨時表可以提高性能,但存在丟失數據的風險。臨時表只在當前會話可見,本會話結束后將自動刪除。如果數據丟失是不可接受的,請使用永久表。
- 臨時表對應的sechema在搜索路徑中的優先級是高於其他sechma的,即臨時表對應schema具有第一搜索優先級。
- \parallel模式不支持創建臨時表!如需使用臨時表,需要在開啟parallel之前就創建好,並在parallel內部使用。parallel內部創建的臨時表不會生效。
- PG_TOTAL_USER_RESOURCE_INFO視圖中的used_temp_space和total_temp_space可以查看當前臨時表的相關空間使用情況
- 創建臨時表時,會同步創建臨時Schema,這些臨時Schema的名稱類似於pg_temp_*和pg_toast_temp_*
- CN Retry功能開啟時會為臨時表數據記錄日志,為保證數據一致性,在使用臨時表時不能切換CN Retry開關狀態,保持使用臨時表的會話中CN Retry開關始終處於打開狀態或者關閉狀態。
- 臨時表和非日志表的存儲方式建議和基表相同。當基表為行存(列存)表時,臨時表和非日志表也推薦創建為行存(列存)表,可以避免行列混合關聯帶來的高計算代價
- 如果上層應用,使用了連接池機制連接GaussDB(DWS),在使用臨時表時,強烈建議將連接歸還連接池之前,將臨時表主動刪除,避免造成連接未斷開導致的數據異常
- 在每個會話第一次使用臨時表之前可以改變temp_buffers的值,之后的設置將是無效的
- autoanalyze不支持對帶有ON COMMIT [DELETE ROWS|DROP]選項的臨時表觸發autoanalyze,如需收集,需用戶手動執行analyze操作
- 如果創建視圖時包含臨時表,則該視圖會自動轉為臨時視圖
典型場景
臨時存儲
臨時表可以減少冗余中間表的存在,在一些復雜操作時,往往需要借助一些中間表去完成功能,但一般來說普通表的創建是需要數據庫管理員來統計創建維護的。臨時表的存在就允許中間表用完即清,減少數據庫系統中冗余表的存在。另外,臨時表在使用時數據是session間隔離的,其他session不能看到當前session的數據,數據安全性在一定程度上也更好。
提升性能
對於過於復雜並且不易通過普通優化方法調整性能的SQL可以考慮拆分的方法,把SQL中某一部分拆分成獨立的SQL並把執行結果存入臨時表,拆分常見的場景包括但不限於:
- 作業中多個SQL有同樣的子查詢,並且子查詢數據量較大。
- Plan cost計算不准,導致子查詢hash bucket太小,比如實際數據1000W行,hash bucket只有1000。
- 函數(如substr,to_number)導致大數據量子查詢選擇度計算不准。
- 多DN環境下對大表做broadcast的子查詢。