存储过程管理复杂结构的核心矛盾是事务边界模糊、参数传递失真、异常不冒泡;SQL Server嵌套需显式控制事务传播,MySQL嵌套须严格遵循DELIMITER与创建顺序,临时表选型及类型匹配直接影响性能与一致性。

复杂存储结构不是“写得长”,而是“锁得久、嵌得深、传得乱”
SQL 存储过程管理复杂结构,核心矛盾从来不是语法会不会写,而是事务边界模糊、参数传递失真、异常不冒泡——这些导致线上跑着跑着就死锁、回滚错、结果空。真正卡住人的,是 BEGIN TRANSACTION 和 SAVE TRANSACTION 的语义误用,不是嵌套层数本身。
SQL Server 中嵌套调用必须显式控制事务传播
外层过程开启事务后,内层过程默认复用同一事务上下文,@@TRANCOUNT 只增不减,但 COMMIT 或 ROLLBACK 会直接作用于最外层。这带来两个致命风险:
- 内层过程里写
COMMIT→ 触发 Msg 3902 “The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION” - 内层过程用
SAVE TRANSACTION sp1_save,外层未捕获异常就ROLLBACK→ 整个事务全丢,而非仅回滚到保存点 - 必须在所有嵌套入口加
SET XACT_ABORT ON,否则内层THROW不中断外层执行流 - 内层过程开头强制加
IF @@TRANCOUNT = 0 BEGIN; RAISERROR('No outer transaction', 16, 1); RETURN; END,避免被单独调用时破坏一致性
MySQL 嵌套过程的 DELIMITER 和创建顺序不能颠倒
你不能在一个 CREATE PROCEDURE 体内再写 CREATE PROCEDURE —— 这会触发 ERROR 1064。真正可行的路径只有一条:先独立创建子过程,再在外层用 CALL 调用。但这个过程极易因 DELIMITER 漏配而静默失败:
- 每个
CREATE PROCEDURE前后必须成对出现DELIMITER //和DELIMITER ; -
DELIMITER作用域是会话级,跨脚本执行时若前一个脚本没重置,后续所有分号都会被忽略 - 子过程名必须全局唯一,重复
CREATE会报错;务必写DROP PROCEDURE IF EXISTS sub_proc_name再建 - 子过程无法返回结果集给外层直接赋值,只能靠
OUT参数或临时表;SELECT输出会被客户端当作独立结果集,干扰主过程逻辑
临时表 vs 表变量:中间状态该存在哪儿?
处理多级聚合、分步筛选、跨模块复用的中间数据时,选错暂存方式会导致性能断崖式下跌:
-
@tmp_table(表变量):无统计信息、不支持非聚集索引、JOIN 时易走嵌套循环,仅适合 ≤ 100 行的小结果 -
#tmp_table(本地临时表):可建索引、有统计信息、支持INSERT ... SELECT批量填充,但命名冲突风险高,需显式DROP TABLE #tmp_table或依赖会话结束自动清理 - 禁止在循环体里反复
INSERT INTO #tmp SELECT ...,应先SELECT ... INTO #tmp一次性生成,再用UPDATE或JOIN修正 - 若中间结果要被多个子过程引用,必须用
##global_tmp(全局临时表),但要注意并发会话间的数据可见性与清理时机
最常被忽略的是:临时表字段类型必须严格匹配源表,比如 INT 字段传入字符串值,会导致隐式转换+索引失效+锁升级——这不是语法错误,是生产环境慢查询的根源。

















