在DB2的日常开发中,临时表是承接中间计算结果的重要工具。DB2将临时表分为两大类:全局临时表(Declared Global Temporary Table,简称DGTT)和创建式全局临时表(Created Global Temporary Table,简称CGTT)。不少开发者对这两者的区别一知半解,常常在存储过程里用错类型,导致出现"SQL0601N 表已存在"或者数据串用的报错。这篇文章就把两种临时表的语法、底层机制和适用场景一次性讲清楚。

一、两种临时表的基本概念与创建语法
首先要明确一点:DB2官方文档中所说的DGTT(声明式全局临时表)和CGTT(创建式全局临时表)都属于"全局临时表"这个大类。所谓全局,指的是表定义对所有会话可见或者可创建,而不是说数据全局共享。恰恰相反,临时表的数据永远都是会话私有的,不同会话各自看到各自的数据副本,这一点后面会详细展开。
两者的核心区别在于定义的存放位置和生命周期。DGTT的表定义不存放在系统目录表中,它只在当前会话(或者说当前连接)内有效,会话断开后表定义自动消失。而CGTT的表定义是持久化的,存放在SYSIBM.SYSTABLES等系统目录里,一旦创建,所有会话都可以直接引用它,只有数据是会话隔离的。DGTT的创建语法如下:
-- 声明式全局临时表
DECLARE GLOBAL TEMPORARY TABLE session.tmp_order (
order_id BIGINT NOT NULL,
cust_id INTEGER,
amount DECIMAL(12,2),
create_time TIMESTAMP
) ON COMMIT PRESERVE ROWS
NOT LOGGED
IN USERSPACE1; -- 也可以省略,放入系统临时表空间
注意DECLARE语句开头的session模式,这是DGTT的固定模式名,声明时可以写也可以不写,系统默认就放在SESSION模式下。CGTT的创建语法则用的是CREATE关键字:
-- 创建式全局临时表
CREATE GLOBAL TEMPORARY TABLE admin.tmp_summary (
dept_id INTEGER NOT NULL,
total_amt DECIMAL(14,2),
cnt INTEGER
) ON COMMIT DELETE ROWS
IN USERSPACE1;
这条语句执行后,表定义被持久化到数据库目录中,任何应用连接上来都可以直接INSERT、SELECT这张表,而不需要先执行DECLARE。这是CGTT相对DGTT最大的便利之处:一次定义,处处可用。
二、数据隔离机制与ON COMMIT选项详解
临时表最容易被误解的一点就是数据隔离。无论是DGTT还是CGTT,DB2都会为每个会话维护一份独立的数据实例。会话A插入的行,会话B是完全看不到的,即使两个会话操作的是同一张CGTT也一样。这个隔离是基于会话级的,不需要开发者做任何额外配置。
ON COMMIT子句决定了事务提交时的行为,这是实际开发中最容易踩坑的地方。ON COMMIT DELETE ROWS表示每次COMMIT后数据自动清空,适合在一个事务内完成的短处理逻辑;ON COMMIT PRESERVE ROWS则表示COMMIT后数据保留,直到会话结束或显式DELETE。如果存储过程内部有多个事务,而中间结果需要在事务之间传递,就必须用PRESERVE ROWS,否则提交一次数据就没了,后面的步骤会莫名其妙查不到数据。
可以用一个简单的实验验证这个行为:
DECLARE GLOBAL TEMPORARY TABLE session.t_test (
id INTEGER
) ON COMMIT DELETE ROWS NOT LOGGED;
INSERT INTO session.t_test VALUES (1), (2), (3);
SELECT COUNT(*) FROM session.t_test; -- 返回3
COMMIT;
SELECT COUNT(*) FROM session.t_test; -- 返回0,数据已被清空
另外还有ON ROLLBACK DELETE ROWS和ON ROLLBACK PRESERVE ROWS选项控制回滚时的行为,默认是PRESERVE ROWS,一般保持默认即可。还有一个NOT LOGGED参数值得注意:临时表默认就不记录日志,写入性能远高于普通表,这也是用它做大批量中间结果缓存的性能优势来源。
三、实际开发中的常见坑点与最佳实践
第一个坑是DGTT的重复声明问题。DGTT不支持CREATE OR REPLACE式的覆盖语法,如果应用程序用了连接池,同一个连接上第二次执行DECLARE就会报SQL0601N。解决办法是在声明前先判断并删除,或者把DECLARE语句放在应用初始化逻辑里只执行一次:
-- 先删除已存在的临时表(如果存在)
DROP TABLE IF EXISTS session.tmp_order;
DECLARE GLOBAL TEMPORARY TABLE session.tmp_order (
order_id BIGINT NOT NULL,
amount DECIMAL(12,2)
) ON COMMIT PRESERVE ROWS NOT LOGGED;
第二个坑是索引与统计信息。DGTT可以在声明时带索引定义,或者在会话内单独CREATE INDEX,但会话结束索引也随之消失。CGTT则可以持久化地创建索引,所有会话共享索引定义。对于数据量大的中间结果集,别忘了建索引,否则复杂的关联查询可能慢得离谱。统计信息方面,CGTT可以像普通表一样执行RUNSTATS让优化器拿到准确的基数估计,这是DGTT做不到的。
第三个坑是空间管理。临时表如果显式指定了IN USERSPACE1这类用户表空间,数据会占用正式表空间的空间,建议为临时表专门建一个临时表空间或者不指定IN子句让系统分配。会话结束后空间会被释放,但如果会话异常挂起不释放,可能需要DBA介入清理。选择建议可以简单归纳为:临时性、单次批处理用DGTT;需要跨程序共享表定义、需要索引和统计信息持久化、需要在编译期校验SQL的,用CGTT。
最后补充一点权限方面的差异:CGTT的创建需要IMPLICIT_DATABASE权限或相应模式上的CREATEIN权限,访问控制走正常的授权体系;DGTT则基本不涉及授权问题,声明即用。理解了这些机制,在存储过程开发、ETL批处理等场景里就能放心地把中间结果放到临时表里,既提升了性能又避免了并发会话之间的数据干扰。