GBase 8a
运维管理
文章

GBase 8a 临时表在会话中间结果处理里的使用边界

发表于2026-05-11 15:45:4722次浏览1个评论

GBase 8a 临时表在会话中间结果处理里的使用边界

我最近整理 GBase 8a 的一些 SQL 写法时,发现临时表这个对象很容易被低估。很多人提到中间结果,第一反应是建一张普通表,跑完再删;也有人把临时表当成“随便用、不会留下痕迹”的工具。真正落到现场时,这两种理解都不够稳。临时表确实适合承接会话内的中间结果,但它不是无成本对象,也不是所有临时加工场景都适合直接上。

GBase 8a MPP Cluster 里,临时表被限制在当前连接中,连接关闭后会自动删除。这个特点对排查和开发都很有价值:同名临时表可以在不同连接里同时存在,也不会和已有同名普通表直接冲突。但这里有一个容易踩的点:临时表会在当前连接里隐藏同名普通表。现场如果连接没有切干净,或者脚本复用了连接,很可能出现“我查的表不是我以为的那张表”。

临时表适合解决什么问题

我自己理解下来,临时表更适合三类场景。

第一类是复杂加工链路里的中间结果沉淀。比如一个月度结算任务,需要先把交易明细按客户、机构、产品做几轮过滤,再和维表做校验。如果每一步都写成一个超长 SQL,后续排查会很难。把关键中间结果落到临时表里,能把问题切小。

第二类是会话级调试。排查数据差异时,我经常会把可疑数据集保存到临时表,然后反复做对比,而不是每次重新跑原始查询。

第三类是避免污染正式库对象。有些临时加工表只在一次会话里有意义,建成普通表会留下清理负担,表名管理也容易混乱。

场景 适合临时表吗 原因 注意点
一次性数据核对 适合 只在当前连接内使用 连接断开后结果消失
多步骤加工调试 适合 便于拆分复杂 SQL 控制数据量和字段数
跨会话共享中间结果 不适合 临时表只属于当前连接 用普通表或结果表
需要备份保留 不适合 临时表不能被备份 改用正式对象
应用连接池复用 谨慎 临时对象生命周期和连接绑定 必须显式清理

这张表里我最关注“连接池复用”。很多开发调试时是一条连接跑完就退出,但应用侧连接池不会这么做。连接如果被复用,临时表就可能在同一个物理连接里继续存在,影响下一段逻辑。

同名对象隐藏是最容易被忽略的点

GBase 8a 文档里提到,两个不同连接可以使用同一个临时表名而不会冲突,也不会与现有同名表冲突,现有表会被隐藏,直到临时表被删除。这个设计在功能上很方便,但排查时需要格外小心。

下面这个例子很贴近现场:

-- 正式业务表
CREATE TABLE rpt_work.tmp_check_result (
    cust_id     bigint,
    acct_month  varchar(6),
    diff_amt    decimal(18,2)
) DISTRIBUTED BY ('cust_id');

-- 某个排查会话里又创建了同名临时表
CREATE TEMPORARY TABLE rpt_work.tmp_check_result (
    cust_id     bigint,
    acct_month  varchar(6),
    diff_amt    decimal(18,2),
    check_tag   varchar(32)
) DISTRIBUTED BY ('cust_id');

INSERT INTO rpt_work.tmp_check_result
VALUES (1002003001, '202604', 18.50, 'session_check');

SELECT * FROM rpt_work.tmp_check_result;

在当前连接里,查询到的是临时表,不是正式表。换一个连接后,同样的 SQL 可能查到正式表。这种现象如果没有提前意识到,会让排查过程变得非常混乱。

我实际处理时一般会给临时表加明显前缀,例如 tmp_sess_tmp_chk_,并避免和正式表命名空间重叠。

命名方式 风险 建议
tmp_check_result 容易和正式对象撞名 加会话或任务标识
result_202604 看不出是否临时对象 不建议
tmp_sess_recon_0427 可读性较好 适合手工排查
tmp_app_stage 应用复用时有残留风险 配合显式 DROP

临时表不是免治理对象

临时表会话结束后自动删除,但这不代表使用过程没有资源成本。GBase 8a 是 MPP 架构,临时表如果承接大量数据,仍然会占用节点上的存储、执行资源和网络传输资源。尤其是把大表过滤结果直接塞进临时表时,不能只想着“反正会自动删”。

我一般会从三个角度控制临时表使用。

第一,字段只保留后续要用的列。不要 SELECT * 直接落临时表。

第二,尽量在写入临时表前完成必要过滤。中间表越宽、越大,后续 Join、聚合成本越高。

第三,分布方式要服务后续操作。临时表也可以指定 DISTRIBUTED BYREPLICATED,不能默认随便建。

CREATE TEMPORARY TABLE tmp_sess_pay_202604 (
    cust_id      bigint,
    org_id       varchar(16),
    pay_month    varchar(6),
    pay_amt      decimal(18,2)
) DISTRIBUTED BY ('cust_id');

INSERT INTO tmp_sess_pay_202604
SELECT cust_id,
       org_id,
       pay_month,
       SUM(pay_amt) AS pay_amt
  FROM ods_pay_detail
 WHERE pay_month = '202604'
   AND pay_status = 'S'
 GROUP BY cust_id, org_id, pay_month;

这里没有把明细全塞进去,而是先按后续需要的粒度聚合。临时表不是垃圾桶,它更像一次排查里的工作台。工作台上放什么,直接影响后面处理效率。

分布方式要跟后续使用对齐

临时表的分布方式经常被忽略。很多人建临时表时只关心字段,没想过后续要怎么关联。GBase 8a 场景里,如果临时表后续要和大事实表按客户号关联,我会优先考虑按客户号分布;如果临时表数据很小、后续要和多张大表关联,可以考虑复制表思路,但要控制数据量。

临时表用途 推荐分布思路 原因 风险
和大表按客户关联 DISTRIBUTED BY ('cust_id') 降低数据重分布 cust_id 倾斜会影响节点
小维表临时补充 REPLICATED 避免多次广播 数据量不能大
仅当前会话单表过滤 按主要过滤或关联列 服务后续查询 不要盲目随机
临时汇总结果 按汇总主键分布 便于二次关联 汇总键过少可能倾斜

我不建议把临时表分布方式当成固定模板。现场要看后续 SQL 怎么写。如果只是单次查看结果,分布方式影响不大;如果后续还有 Join、聚合、导出,分布方式就会变成关键细节。

应用脚本里要显式清理

虽然临时表会在连接关闭时自动删除,但我在脚本里仍然倾向于显式 DROP TEMPORARY TABLE。原因很简单:不是所有脚本都能保证连接马上关闭,也不是所有异常分支都能自然走到退出。

一个比较稳的写法是开始前先尝试清理,再创建临时表,结束后再清理一次。

DROP TEMPORARY TABLE IF EXISTS tmp_sess_order_delta;

CREATE TEMPORARY TABLE tmp_sess_order_delta (
    order_id      bigint,
    cust_id       bigint,
    order_day     date,
    delta_amt     decimal(18,2)
) DISTRIBUTED BY ('cust_id');

INSERT INTO tmp_sess_order_delta
SELECT order_id,
       cust_id,
       order_day,
       pay_amt - refund_amt AS delta_amt
  FROM dwd_order_day
 WHERE order_day >= '2026-04-01'
   AND order_day <  '2026-05-01'
   AND pay_amt <> refund_amt;

SELECT cust_id, SUM(delta_amt) AS sum_delta
  FROM tmp_sess_order_delta
 GROUP BY cust_id
 HAVING SUM(delta_amt) <> 0;

DROP TEMPORARY TABLE IF EXISTS tmp_sess_order_delta;

Shell 脚本里也可以把清理逻辑放到退出路径中:

#!/bin/bash
set -e

DB_HOST="192.0.2.41"
DB_USER="etl_check"
DB_NAME="risk_dw"

gbase -h"${DB_HOST}" -u"${DB_USER}" "${DB_NAME}" <<'SQL'
DROP TEMPORARY TABLE IF EXISTS tmp_sess_risk_order;

CREATE TEMPORARY TABLE tmp_sess_risk_order (
    cust_id bigint,
    risk_day date,
    risk_score decimal(10,4)
) DISTRIBUTED BY ('cust_id');

INSERT INTO tmp_sess_risk_order
SELECT cust_id, risk_day, risk_score
  FROM ads_risk_score_day
 WHERE risk_day = '2026-04-30'
   AND risk_score >= 0.85;

SELECT COUNT(*) AS high_risk_cnt
  FROM tmp_sess_risk_order;

DROP TEMPORARY TABLE IF EXISTS tmp_sess_risk_order;
SQL

我自己更倾向于把临时表看成脚本内部变量,而不是数据库长期对象。变量用完要释放,临时表也是一样。

常见坑和处理建议

常见坑 现场表现 原因 建议
临时表和正式表同名 同一 SQL 不同连接结果不同 当前连接隐藏正式表 临时表命名加专用前缀
应用连接池复用 下一次请求看到上次临时表 连接未关闭 开始和结束都显式 DROP
SELECT * 建临时表 中间结果过宽过大 字段未裁剪 只保留后续必要字段
不指定分布方式 后续 Join 成本高 没贴合关联键 按后续访问方式设计
把临时表当结果表 连接断开数据丢失 生命周期误判 需要保留时用普通表
期望备份临时表 备份中找不到 临时表不参与备份 不承载长期结果

结尾总结

GBase 8a 临时表很适合承接会话内中间结果,也很适合排查复杂 SQL 时把问题拆小。但它的边界也很清楚:生命周期绑定连接,不能备份,可能隐藏同名正式表,数据量大时仍然会带来资源成本。

我最近整理下来觉得,临时表用得好不好,关键不在语法,而在对象边界。命名要和正式表拉开,分布方式要和后续 SQL 对齐,脚本里要显式清理,应用连接池场景要格外谨慎。把这些细节处理好,临时表会是很实用的排查和加工工具;如果只把它当成“临时凑一下”的对象,后面反而容易制造新的现场问题。

参考资料

[1] GBase 8a DDL语法 CREATE TEMPORARY TABLE https://www.gbase.cn/docs/gbase-8a/%E4%BA%A7%E5%93%81%E6%89%8B%E5%86%8C/dm-database-management-guide/dm-sql-reference/dm-syntax-ddl
[2] GBase 8a 系统表 https://www.gbase.cn/docs/gbase-8a/%E4%BA%A7%E5%93%81%E6%89%8B%E5%86%8C/dm-database-management-guide/dm-system-table/
[3] GBase 社区优质文章区 https://www.gbase.cn/community/section/11

评论

登录后才可以发表评论
用户头像
柒柒天晴发表于 3个月前
继续努力