中间表必须用带命名前缀的物理表而非#temp或@table变量,因其支持跨会话访问、索引、事务日志及容错操作;必含batch_id、received_at、raw_payload、status、error_code、error_message等元数据字段;需建唯一约束防重,并通过MERGE+幂等ID保障并发安全;清洗逻辑须封装独立存储过程,强制类型转换校验与外键验证,批量更新状态。

为什么中间表不能直接用 #temp 或 @table 变量
第三方接口数据落地必须持久化,临时表(#temp)在存储过程退出后自动销毁,@table 变量生命周期更短、不支持索引和统计信息,两者都无法支撑后续校验、重试、审计等容错动作。真实场景中,你得能查到“昨天失败的那 17 条订单到底卡在哪一步”。
-
#temp表无法被其他会话或后续作业访问,重试逻辑写在另一个 SP 里就查不到数据 -
@table变量不记录事务日志,崩溃恢复后内容丢失,且 SQL Server 不允许对它建索引,大批量插入后查询极慢 - 真正可用的是带命名前缀的物理中间表,如
stg_orders_from_paypal_202609或统一用stg_inbound_batch+session_id字段做软隔离
中间表字段设计必须包含哪些容错元数据
只存原始字段是自找麻烦。没上下文信息,出错时连“这条数据是哪次同步来的”都得翻日志猜。
- 必加字段:
batch_id VARCHAR(50)(调用方传入或由NEWID()生成)、received_at DATETIME2(入库时间,非接口返回时间)、raw_payload NVARCHAR(MAX)(完整 JSON/XML 原文,用于重放) - 状态字段至少三态:
status TINYINT DEFAULT 0(0=received, 1=processed, -1=failed),别用字符串枚举,避免大小写/空格污染 - 错误追踪字段:
error_code VARCHAR(20)(如'PARSE_ERR'、'FK_VIOLATION')、error_message NVARCHAR(500)(截断但保留关键堆栈片段) - 禁止把
status当业务字段用——它只反映本次落地是否成功,不表示订单是否支付成功
如何防止重复写入和并发冲突
第三方接口可能重发、前端可能双击提交、调度任务可能漂移,中间表没防重机制,轻则脏数据,重则主键冲突中断整个批次。
- 在中间表上建唯一约束,组合字段如
(batch_id, external_ref_id),其中external_ref_id是接口方提供的幂等 ID(不是你自己生成的) - 插入时用
MERGE而非INSERT:匹配则更新status和received_at,不匹配才插入,避免报错中断 - 不要依赖应用层“先查再插”,网络延迟+并发下必然漏;数据库层唯一约束才是最终防线
- 若接口无幂等 ID,退而求其次用
HASHBYTES('SHA2_256', raw_payload)生成摘要,但需注意 NULL 和空白字符处理一致性
中间表数据怎么安全流转到业务表
从中间表清洗进 orders 或 customers 是最易出错环节。硬编码字段映射、忽略空值、跳过约束检查,都会让问题数据悄悄流进核心表。
- 清洗逻辑必须封装在独立 SP 中(如
usp_process_stg_orders),与接收 SP 解耦,便于单独测试和灰度发布 - 每条 UPDATE/INSERT 前加
IF EXISTS (SELECT 1 FROM business_table WHERE id = @id)校验外键,失败则设status = -1并记error_code = 'FK_VIOLATION' - 数值字段强制用
TRY_CAST(raw_value AS DECIMAL(18,2)),失败则留 NULL 并记录error_code = 'CAST_FAIL',绝不静默转成 0 - 清洗完成后,用
UPDATE stg SET status = 1 WHERE batch_id = @batch_id AND status = 0批量标记成功,避免逐行 COMMIT 拖慢性能
batch_id,排查就得翻三天日志;没加唯一约束,半夜重跑脚本可能把上周订单覆盖掉。容错不在代码行数多,而在每个字段、每条语句都预设了“它一定会坏”的前提。

















