Skip to content

临时表与非日志表(TEMPORARY / UNLOGGED) ​

不是所有表都需要"永久 + 可靠"。做报表中间计算时,数据用完即弃;做可重建的缓存时,丢了也无所谓。PostgreSQL 为这两类场景提供了 临时表(TEMPORARY TABLE) 与 非日志表(UNLOGGED TABLE),它们以牺牲持久性换取写入性能与清理便利。本节讲清三者的差异、代价与连接池下的陷阱。

前置知识

阅读本节前,建议先了解:CREATE TABLE AS 与 SELECT INTO

基础概念 ​

三种表的定位差异,是本节的核心对照表:

维度永久表(普通表)临时表(TEMPORARY)非日志表(UNLOGGED)
写入 WAL是否(仅少量元数据)否(仅少量元数据)
崩溃后数据完好会话结束即消失数据被清空(表结构保留)
是否可复制到备库是否是(但备库上数据为空)
可见范围所有会话(受权限约束)仅创建它的会话所有会话
生命周期永久会话结束 / 事务结束(取决于 ON COMMIT)永久
存放位置数据库目录临时模式 pg_temp_N数据库目录
写入性能基准最快(无 WAL)快(无 WAL)
适用场景业务数据会话内中间结果可重建的缓存、日志落库前的暂存

几个关键概念:

  • 临时表(Temporary Table):名字空间独立,多个会话可以各建一张同名临时表而互不冲突。它存在于 pg_temp_<N> 这个会话私有的模式里,search_path 会自动优先解析到它。
  • ON COMMIT 三种行为:PRESERVE ROWS(默认,数据保留到会话结束)、DELETE ROWS(每次事务提交即清空)、DROP(事务提交即删表)。这是临时表最实用也最常被忽略的选项。
  • 非日志表(Unlogged Table):表结构会写入 WAL 并在备库上重建,但数据不写 WAL。因此备库上这张表始终为空,崩溃恢复时(包括备库提升为主库)数据会被全部清空。
  • temp_buffers:临时表专用缓冲区,默认 8MB,按会话设置。它独立于 shared_buffers。
  • pg_temp_N 模式:PostgreSQL 自动为每个会话分配,编号动态变化,因此不能硬编码。

PostgreSQL 中还有第三类"快但更危险"的选择——UNLOGGED + 关闭 synchronous_commit,以及完全不落盘的内存表变体。本节只覆盖前两类实际可用的方案。

语法与用法 ​

text
-- ① 临时表
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE [ IF NOT EXISTS ] table_name (
    ...
) [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
  [ WITH ( storage_parameter [= value] [, ... ] ) ]
  [ TABLESPACE tablespace_name ]

-- ② 非日志表
CREATE UNLOGGED TABLE [ IF NOT EXISTS ] table_name (
    ...
)

-- ③ 状态切换
ALTER TABLE table_name SET LOGGED;
ALTER TABLE table_name SET UNLOGGED;

GLOBAL 与 LOCAL 都是语法噪音——PostgreSQL 中两者完全等价,且临时表永远是"会话局部"的(与 SQL 标准的理解不同)。官方建议直接忽略这两个关键字。

sql
-- 临时表:会话结束自动删除
CREATE TEMP TABLE tmp_orders (
  id     bigint,
  amount numeric(12,2)
);
text
CREATE TABLE
sql
-- 事务提交即清空(适合"每事务重置的累加器")
CREATE TEMP TABLE tmp_counter (
  sku   text PRIMARY KEY,
  qty   integer NOT NULL DEFAULT 0
) ON COMMIT DELETE ROWS;

-- 事务结束即删表(适合一次性流程)
CREATE TEMP TABLE tmp_once (
  v text
) ON COMMIT DROP;
sql
-- 非日志表:永久存在,但崩溃后数据清空
CREATE UNLOGGED TABLE session_cache (
  cache_key  text PRIMARY KEY,
  cache_val  jsonb NOT NULL,
  expires_at timestamptz NOT NULL
);
text
CREATE TABLE

详细说明 ​

临时表:命名空间与可见性 ​

临时表的数据对其他会话完全不可见,这是它与普通表最本质的差别:

sql
-- 会话 A
CREATE TEMP TABLE tmp_demo (x int);
INSERT INTO tmp_demo VALUES (1), (2);

-- 查询当前会话能看到的所有 schema
SELECT current_schemas(true);
text
                 current_schemas
--------------------------------------------------
 {pg_catalog,pg_temp_3,public}
(1 row)

pg_temp_3 就是本会话的临时模式,它排在 public 前面,因此同名对象会被临时表"遮蔽":

sql
-- 危险!临时表会遮蔽同名永久表
CREATE TABLE metrics (id int, value numeric);          -- 永久表
CREATE TEMP TABLE metrics (id int);                    -- 临时表,遮蔽前者

SELECT * FROM metrics;      -- 访问的是临时表!
INSERT INTO metrics VALUES (1);   -- 写入临时表!
sql
-- 想明确访问永久表,必须写全限定名
SELECT * FROM public.metrics;

这也是连接池环境下最危险的一类 bug:临时表随会话存在,而连接池会把物理连接复用给不同的逻辑请求。

sql
-- 查看当前会话的临时表
SELECT n.nspname AS schema, c.relname AS temp_table, pg_size_pretty(pg_total_relation_size(c.oid)) AS size
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname LIKE 'pg_temp%' AND c.relkind = 'r';

ON COMMIT 三种行为 ​

sql
-- ① PRESERVE ROWS(默认):数据保留到会话结束
CREATE TEMP TABLE t_preserve (x int) ON COMMIT PRESERVE ROWS;
BEGIN;
INSERT INTO t_preserve VALUES (1);
COMMIT;
SELECT count(*) FROM t_preserve;   -- 1,事务提交后数据还在
text
 count
-------
     1
(1 row)
sql
-- ② DELETE ROWS:每次提交后清空
CREATE TEMP TABLE t_delete (x int) ON COMMIT DELETE ROWS;
BEGIN;
INSERT INTO t_delete VALUES (1);
COMMIT;
SELECT count(*) FROM t_delete;     -- 0,已被清空
text
 count
-------
     0
(1 row)
sql
-- ③ DROP:提交后连表一起删
CREATE TEMP TABLE t_drop (x int) ON COMMIT DROP;
BEGIN;
INSERT INTO t_drop VALUES (1);
COMMIT;
SELECT count(*) FROM t_drop;       -- 报错:表不存在
text
ERROR:  relation "t_drop" does not exist

注意 ON COMMIT DROP 只在事务块内才体现差别:如果不在 BEGIN 里,CREATE TEMP TABLE ... ON COMMIT DROP 会在语句自己的隐式事务提交时立刻把表删掉,后续无法使用。它只适合"在事务内建表、用完即删"的场景。

ON COMMIT数据何时消失表何时消失典型用途
PRESERVE ROWS(默认)会话结束会话结束会话内多次复用
DELETE ROWS每次事务提交会话结束事务级累加器、批处理暂存
DROP—事务提交时事务内一次性的中间表

非日志表:崩溃后数据清空 ​

UNLOGGED 的收益来自"跳过 WAL 写入",代价是放弃崩溃恢复能力:

sql
-- 建一张非日志表并写入数据
CREATE UNLOGGED TABLE ul_demo (id int, payload text);
INSERT INTO ul_demo SELECT g, repeat('x', 100) FROM generate_series(1, 100000) g;
text
CREATE TABLE
INSERT 0 100000
sql
-- 查看表的持久化属性
SELECT relname, relpersistence,
       CASE relpersistence WHEN 'p' THEN '永久(permanent)'
                           WHEN 'u' THEN '非日志(unlogged)'
                           WHEN 't' THEN '临时(temporary)' END AS kind
FROM pg_class WHERE relname IN ('ul_demo', 'tmp_demo');
text
 relname  | relpersistence |        kind
----------+----------------+--------------------
 ul_demo  | u              | 非日志(unlogged)
(1 row)

relpersistence 的取值是判断表类型的可靠依据:p = permanent、u = unlogged、t = temporary。

崩溃后的实际后果:非日志表在崩溃恢复(或流复制备库提升)时会被整体清空。PostgreSQL 会在恢复阶段检测到非日志表的 init fork 与实际数据不一致,直接丢弃整张表的数据。

sql
-- 模拟崩溃的等效验证:查看非日志表的 init fork 存在性
SELECT pg_relation_filepath('ul_demo') AS main_file;
text
       main_file
------------------------
 base/16385/24813
(1 row)
sql
-- 对比 WAL 写入量:UNLOGGED 几乎不写 WAL
SELECT pg_current_wal_lsn() AS before_lsn \gset

INSERT INTO ul_demo SELECT g, repeat('y', 100) FROM generate_series(1, 100000) g;
SELECT pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), :'before_lsn')) AS wal_for_unlogged;
text
INSERT 0 100000

 wal_for_unlogged
------------------
 4680 kB
(1 row)

对比永久表写入同样数据量时会产生的 WAL 量(通常为数据量的 1.5~2 倍),UNLOGGED 的收益非常可观。但收益的前提是"数据可重建"——如果丢了要能从其他地方重新算出来,才适合用。

类型转换:SET LOGGED / SET UNLOGGED ​

两种属性之间可以互相转换,但都要重写整表、且持 ACCESS EXCLUSIVE 锁:

sql
-- 永久表 → 非日志表(重写整表)
ALTER TABLE big_table SET UNLOGGED;

-- 非日志表 → 永久表(重写整表,补齐 WAL)
ALTER TABLE big_table SET LOGGED;
sql
-- 临时表不能改属性
ALTER TABLE tmp_demo SET LOGGED;
text
ERROR:  cannot change logged status of table "tmp_demo"
DETAIL:  It is temporary.

转换过程中会重写整表,这与 ALTER COLUMN TYPE 的代价同级,大表上必须放到维护窗口。

临时表与 temp_buffers ​

临时表的数据默认走 temp_buffers(会话级,默认 8MB),不参与 shared_buffers 的争用。这意味着临时表不会污染共享缓存——这是它除了"省 WAL"外的第二个重要优点。

sql
-- 查看与调整当前会话的临时缓冲区
SHOW temp_buffers;
text
 temp_buffers
--------------
 8MB
(1 row)
sql
-- 临时表前调大(必须是会话中第一条语句之前,或至少在首次使用临时表前)
SET temp_buffers = '64MB';
sql
-- 查看临时表占用的磁盘空间(临时文件位置由 temp_tablespaces 决定)
SHOW temp_tablespaces;

若 temp_tablespaces 为空,临时表与排序临时文件会写到默认表空间(pg_default),与业务数据抢同一块磁盘。生产环境通常单独挂一块盘给临时数据:

sql
-- 建一个专门的临时表空间(需要超级用户)
CREATE TABLESPACE ts_temp LOCATION '/data/pg_tmp';
ALTER SYSTEM SET temp_tablespaces = 'ts_temp';
SELECT pg_reload_conf();

连接池环境下的陷阱 ​

这是临时表在生产上翻车最多的地方。连接池(pgbouncer 的 transaction / statement 池化模式、或应用侧连接池)会把同一个物理连接先后交给不同逻辑请求,于是:

陷阱现象缓解方式
临时表"残留"上一个请求建的临时表,被下一个请求看到并使用用 ON COMMIT DROP / DELETE ROWS,或每次显式 DROP TABLE IF EXISTS
临时表遮蔽永久表逻辑请求误查到同名临时表,数据"要少一部分"关键查询写全限定名 public.t
temp_buffers 设置丢失SET 只对当前会话有效,换连接后失效在连接重置脚本中设置
临时表目录膨胀会话不结束则临时表不释放,长期连接池连接越积越多连接归还前执行 DISCARD ALL
pg_temp 注入恶意会话通过同名临时表劫持函数内的表引用函数内使用 SET search_path = pg_catalog, pg_temp 或全限定名
sql
-- 连接归还前的标准清理动作
DISCARD ALL;   -- 等价于 RESET ALL + DEALLOCATE ALL + CLOSE ALL + UNLISTEN * + DISCARD PLANS + DISCARD TEMP
sql
-- 用 DROP TEMP 清理当前会话所有临时表
DROP TABLE IF EXISTS tmp_a, tmp_b;

实战示例 ​

场景:为报表系统做一个"当日销售汇总"流程,要求——① 中间计算用临时表,会话结束自动清理;② 高频读取的汇率换算结果用非日志表做缓存;③ 验证三者在 WAL、可见性、崩溃行为上的差异。

第 1 步:准备基础表

sql
DROP TABLE IF EXISTS orders CASCADE;
DROP TABLE IF EXISTS users CASCADE;
DROP TABLE IF EXISTS session_cache;
DROP TABLE IF EXISTS ul_wal_demo;

CREATE TABLE users (
  id    bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  email text NOT NULL UNIQUE
);

CREATE TABLE orders (
  id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  user_id    bigint NOT NULL REFERENCES users (id),
  amount     numeric(12,2) NOT NULL,
  status     text NOT NULL DEFAULT 'paid',
  created_at timestamptz NOT NULL DEFAULT now()
);

INSERT INTO users (email) SELECT 'u' || g || '@example.com' FROM generate_series(1, 50) g;
INSERT INTO orders (user_id, amount, status, created_at)
SELECT (g % 50) + 1, (random() * 500 + 5)::numeric(12,2), 'paid', now() - (g % 30) * interval '1 day'
FROM generate_series(1, 30000) g;
text
CREATE TABLE
CREATE TABLE
INSERT 0 50
INSERT 0 30000

第 2 步:用临时表做会话内中间计算

sql
-- 每事务重置的累加器
CREATE TEMP TABLE tmp_user_amount (
  user_id bigint PRIMARY KEY,
  total   numeric(16,2) NOT NULL DEFAULT 0,
  cnt     integer NOT NULL DEFAULT 0
) ON COMMIT DELETE ROWS;

-- 一次填充
INSERT INTO tmp_user_amount (user_id, total, cnt)
SELECT user_id, sum(amount), count(*)
FROM orders WHERE status = 'paid'
GROUP BY user_id;

-- 关联查询:拿到用户维度汇总
SELECT t.user_id, u.email, t.cnt, t.total
FROM tmp_user_amount t
JOIN users u ON u.id = t.user_id
ORDER BY t.total DESC
LIMIT 5;
text
INSERT 0 50

 user_id |       email        | cnt  |   total
---------+--------------------+------+------------
      17 | u17@example.com    |   60 |  15x
...
(5 rows)
sql
-- 确认临时表位于会话私有 schema 中
SELECT n.nspname AS schema, c.relname, c.relpersistence
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = 'tmp_user_amount';
text
    schema    |     relname      | relpersistence
--------------+------------------+----------------
 pg_temp_3    | tmp_user_amount  | t
(1 row)

第 3 步:验证 ON COMMIT DELETE ROWS 的效果

sql
BEGIN;
INSERT INTO tmp_user_amount (user_id, total, cnt) VALUES (999, 1, 1);
SELECT count(*) AS inside_tx FROM tmp_user_amount WHERE user_id = 999;
COMMIT;
text
 count
-------
     1
(1 row)

COMMIT
sql
-- 提交后自动清空
SELECT count(*) AS after_commit FROM tmp_user_amount;
text
 after_commit
--------------
         0
(1 row)

第 4 步:临时表与永久表同名遮蔽演示

sql
-- 建一张永久表
CREATE TABLE metrics (id bigint, note text);
INSERT INTO metrics VALUES (1, '永久表的数据');

-- 同会话内建同名临时表
CREATE TEMP TABLE metrics (id bigint);

-- 不带模式名的查询命中的是临时表
SELECT count(*) AS from_which FROM metrics;
text
CREATE TABLE
INSERT 0 1
CREATE TABLE

 from_which
------------
          0
(1 row)
sql
-- 想访问永久表必须写全限定名
SELECT * FROM public.metrics;
text
 id |      note
----+------------------
  1 | 永久表的数据
(1 row)

第 5 步:用非日志表做缓存

sql
CREATE UNLOGGED TABLE session_cache (
  cache_key  text PRIMARY KEY,
  cache_val  jsonb NOT NULL,
  expires_at timestamptz NOT NULL DEFAULT now() + interval '1 hour'
);

INSERT INTO session_cache (cache_key, cache_val)
VALUES
  ('rate:USD:CNY', '{"rate": 7.12}'),
  ('rate:EUR:CNY', '{"rate": 7.78}')
ON CONFLICT (cache_key) DO UPDATE SET cache_val = EXCLUDED.cache_val;

SELECT cache_key, cache_val FROM session_cache ORDER BY cache_key;
text
CREATE TABLE
INSERT 0 2

   cache_key   |     cache_val
---------------+--------------------
 rate:EUR:CNY  | {"rate": 7.78}
 rate:USD:CNY  | {"rate": 7.12}
(2 rows)

第 6 步:对比 WAL 写入量

sql
-- 构造两张同结构的表,一张永久、一张非日志
CREATE TABLE logged_demo   (id int, payload text);
CREATE UNLOGGED TABLE unlogged_demo (id int, payload text);

-- 先记下 LSN
SELECT pg_current_wal_lsn() AS lsn_before \gset
text
CREATE TABLE
CREATE TABLE
sql
-- 往永久表写入 10 万行
INSERT INTO logged_demo SELECT g, repeat('x', 100) FROM generate_series(1, 100000) g;
SELECT pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), :'lsn_before')) AS wal_logged;
text
INSERT 0 100000

 wal_logged
------------
 18 MB
(1 row)
sql
-- 往非日志表写入同样 10 万行
SELECT pg_current_wal_lsn() AS lsn_before2 \gset
INSERT INTO unlogged_demo SELECT g, repeat('x', 100) FROM generate_series(1, 100000) g;
SELECT pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), :'lsn_before2')) AS wal_unlogged;
text
INSERT 0 100000

 wal_unlogged
--------------
 4704 kB
(1 row)

以上数值为典型量级示意(100 字节/行、10 万行),实际结果随 wal_level、full_page_writes、页填充率与压缩设置而变。结论是稳定的:非日志表的 WAL 量通常只有永久表的三分之一到五分之一(差额来自行数据本身不写 WAL,仅剩元数据与检查点记录)。

第 7 步:属性互转的代价

sql
-- 非日志 → 永久:重写整表并补齐 WAL
ALTER TABLE unlogged_demo SET LOGGED;

SELECT relname, relpersistence FROM pg_class WHERE relname IN ('logged_demo', 'unlogged_demo');
text
ALTER TABLE

   relname     | relpersistence
---------------+----------------
 logged_demo   | p
 unlogged_demo | p
(2 rows)
sql
-- 永久 → 非日志:同样重写
ALTER TABLE logged_demo SET UNLOGGED;
text
ALTER TABLE

第 8 步:清理

sql
DROP TABLE IF EXISTS logged_demo, unlogged_demo, session_cache, metrics;
DROP TABLE IF EXISTS orders, users CASCADE;
DROP TABLE IF EXISTS tmp_user_amount;
text
DROP TABLE
DROP TABLE
DROP TABLE

注意事项 ​

  • 临时表在连接池下会跨请求"残留"。 这是生产事故的高发区,必须显式清理或使用 ON COMMIT DROP。
sql
-- 危险!下一个逻辑请求可能看到这些数据
CREATE TEMP TABLE stage (id int);

-- 安全:或用 ON COMMIT DROP,或在归还连接前 DISCARD TEMP
DROP TABLE IF EXISTS stage;
  • 临时表会遮蔽同名永久表。 关键查询务必用全限定名。
sql
-- 危险!实际查的是临时表
SELECT count(*) FROM metrics;

-- 正确
SELECT count(*) FROM public.metrics;
  • ON COMMIT DROP 在非事务块中会立即删表。
sql
-- 表立刻消失,后续无法使用
CREATE TEMP TABLE t (x int) ON COMMIT DROP;
SELECT * FROM t;
text
ERROR:  relation "t" does not exist
  • 临时表不支持 SET LOGGED。
sql
-- 错误!
ALTER TABLE tmp_demo SET LOGGED;
text
ERROR:  cannot change logged status of table "tmp_demo"
DETAIL:  It is temporary.
  • 非日志表在崩溃或备库提升后数据全部丢失。 只放可重建的数据。
sql
-- 危险!订单这类不可重建的业务数据放进非日志表
CREATE UNLOGGED TABLE orders (...);   -- 崩溃后订单全没了
  • 非日志表在流复制备库上始终为空。 不要用它做"主备一致性"相关的逻辑。
sql
-- 备库上查询非日志表会得到 0 行
SELECT count(*) FROM session_cache;   -- 0
  • SET LOGGED / SET UNLOGGED 会重写整表并持 ACCESS EXCLUSIVE 锁。 大表上必须在维护窗口执行。
sql
-- 大表转换前先确认磁盘空间
SELECT pg_size_pretty(pg_total_relation_size('big_table')) AS need_space;
ALTER TABLE big_table SET UNLOGGED;
  • 临时表不参与 shared_buffers,走 temp_buffers。 默认 8MB 对复杂分析偏小。
sql
-- 在会话开始时调大
SET temp_buffers = '128MB';
  • temp_buffers 必须在首次使用临时表/排序之前设置,否则本会话不生效。

  • 临时表默认与主数据共用一个磁盘。 生产环境应设置独立的 temp_tablespaces。

sql
CREATE TABLESPACE ts_temp LOCATION '/data/pg_tmp';
ALTER SYSTEM SET temp_tablespaces = 'ts_temp';
  • 临时表不支持某些特性:不能建外键引用、不能创建其他对象引用它、不支持逻辑复制。
sql
-- 错误!foreign key constraints cannot be used with temporary tables
ALTER TABLE tmp_orders ADD FOREIGN KEY (id) REFERENCES public.orders (id);
  • pg_temp_N 的编号不能硬编码。 它是会话级动态分配,不同会话编号不同。
sql
-- 错误!编号会变
SELECT * FROM pg_temp_3.tmp_demo;

-- 正确:用 pg_temp 别名(每个会话都指向自己的临时模式)
SELECT * FROM pg_temp.tmp_demo;
  • 临时表上有 ON COMMIT DELETE ROWS 时,事务内的自增/序列不会重置。

  • UNLOGGED 表可以建索引、约束、触发器,功能与永久表一致,仅持久化属性不同。

  • DISCARD ALL 会清理所有临时表与所有会话状态,是连接归还前的标准动作。

sql
DISCARD ALL;
  • 逻辑复制不支持无主键表,也不复制非日志表的定义(仅复制了结构时数据为空)。

最佳实践 ​

  • 临时表一律显式指定 ON COMMIT,让生命周期一目了然,不依赖默认值。
sql
-- 会话内多次复用
CREATE TEMP TABLE tmp_base (...) ON COMMIT PRESERVE ROWS;

-- 事务级暂存
CREATE TEMP TABLE tmp_stage (...) ON COMMIT DELETE ROWS;

-- 事务内一次性
CREATE TEMP TABLE tmp_once (...) ON COMMIT DROP;
  • ETL 中间表统一用 CREATE TEMP TABLE ... AS,省去手动建表与清理。
sql
CREATE TEMP TABLE tmp_stage AS
SELECT id, user_id, amount FROM orders WHERE status = 'paid';
  • 关键查询写全限定名 public.表名,彻底规避临时表遮蔽风险。
sql
SELECT count(*) FROM public.orders WHERE status = 'paid';
  • 连接池配置 DISCARD ALL 为连接归还动作,防止临时表与 search_path 污染。
sql
-- 应用侧在归还连接前执行
DISCARD ALL;
  • 非日志表只用于可重建的缓存/中间数据,并在应用层实现"缓存失效即重建"逻辑。
sql
CREATE UNLOGGED TABLE rate_cache (
  pair       text PRIMARY KEY,
  rate       numeric(12,6) NOT NULL,
  updated_at timestamptz NOT NULL DEFAULT now()
);
  • 为临时数据单独建表空间,避免影响主数据的 IO。
sql
CREATE TABLESPACE ts_temp LOCATION '/data/pg_tmp';
SET temp_tablespaces = 'ts_temp';
  • 临时表会话开始时调大 temp_buffers,尤其是复杂分析类会话。
sql
SET temp_buffers = '128MB';
  • 不要用临时表传递"跨请求"状态。 那是应用缓存该做的事,用数据库临时表做会在连接池下出错。

  • 把 UNLOGGED 表纳入备份检查清单:pg_dump 会导出其结构,但数据在崩溃后为空,恢复时要有重建脚本。

  • 上线前用 pg_class.relpersistence 复核,确认表的持久化属性符合设计。

sql
SELECT relname, relpersistence
FROM pg_class
WHERE relnamespace = 'public'::regnamespace AND relkind = 'r'
ORDER BY relpersistence, relname;
  • 临时表不适合放大量数据。 需要处理百万级中间结果时,优先用 UNLOGGED + 显式清理,或直接改进查询(避免物化中间结果)。

  • DROP TABLE IF EXISTS 与 CREATE TEMP TABLE 组合使用,让脚本在连接复用时也能安全执行。

sql
DROP TABLE IF EXISTS tmp_stage;
CREATE TEMP TABLE tmp_stage AS SELECT ...;

参考链接 ​