SQL Server 存储过程设计规范(事务与异常处理)

一、背景与目标

在中大型业务系统中,存储过程往往被多处调用、层层嵌套,一旦事务和异常处理设计不规范,极易导致以下问题:

  • 子存储过程误提交或回滚父事务

  • 事务悬挂(未提交 / 未回滚)

  • 异常被吞掉,调用方无法感知失败

  • 生产环境问题难以定位

因此,必须建立一套统一、强约束、可组合的存储过程设计规范。

本文给出一套行业级 SQL Server 存储过程事务规范,并解释其设计原理。


二、核心设计原则

原则 1:存储过程不擅自管理外部事务

  • 存储过程只在没有事务时才开启事务

  • 不擅自 COMMIT / ROLLBACK 调用方事务

  • 支持多层存储过程安全嵌套

存储过程必须是"事务友好型组件",而不是事务的拥有者。


原则 2:事务的开启、提交、回滚必须成对、可控

统一通过一个本地变量标识事务是否由当前存储过程开启:

复制代码
DECLARE @TranStarted BIT = 0; 

原则 3:TRY...CATCH 是强制要求

  • 禁止裸写 BEGIN TRAN / COMMIT

  • 禁止不捕获异常

  • 禁止在 CATCH 中继续执行业务 SQL


原则 4:异常必须可追踪、可传播

  • 记录错误日志(表或统一过程)

  • 对外部调用者:

    • 接口型存储过程 → 返回 code / msg

    • 业务型存储过程 → THROW 抛出异常


三、统一事务控制模板(强制)

标准事务控制骨架

sql 复制代码
SET NOCOUNT ON;
SET IMPLICIT_TRANSACTIONS OFF;

DECLARE @TranStarted BIT = 0;

BEGIN TRY
    IF @@TRANCOUNT = 0
    BEGIN
        BEGIN TRAN;
        SET @TranStarted = 1;
    END

    -- ======================
    -- 业务逻辑
    -- ======================

    IF @TranStarted = 1
    BEGIN
        COMMIT;
    END
END TRY
BEGIN CATCH
    IF @TranStarted = 1 AND XACT_STATE() <> 0
    BEGIN
        ROLLBACK;
    END

    -- 统一错误处理
    EXEC dbo.proc_error_log;

    -- 异常传播策略由类型决定
END CATCH

四、两类存储过程的规范区分

1️⃣ 接口型存储过程(返回 code / msg)

适用场景

  • 被 Java / Go / .NET 直接调用

  • 作为接口的"最终边界"

  • 前端或服务端需要明确状态码

规范示例(你给出的 nb_tran_demo1
sql 复制代码
CREATE PROCEDURE dbo.nb_tran_demo1
AS
BEGIN
    SET NOCOUNT ON;
    SET IMPLICIT_TRANSACTIONS OFF;

    DECLARE @TranStarted BIT = 0;

    BEGIN TRY
        IF @@TRANCOUNT = 0
        BEGIN
            BEGIN TRAN;
            SET @TranStarted = 1;
        END

        -- ======================
        -- 业务逻辑
        -- ======================

        IF @TranStarted = 1
        BEGIN
            COMMIT;
        END

        SELECT 200 AS code, '成功' AS msg;
    END TRY
    BEGIN CATCH
        IF @TranStarted = 1 AND XACT_STATE() <> 0
        BEGIN
            ROLLBACK;
        END

        EXEC dbo.proc_error_log;

        SELECT 501 AS code, '系统处理失败' AS msg;
    END CATCH
END
设计要点
  • ❌ 不使用 THROW

  • ✅ 异常被转换为业务可识别结果

  • ✅ 保证接口幂等、稳定


2️⃣ 业务型存储过程(必须 THROW)

适用场景

  • 被其他存储过程调用

  • 业务逻辑拆分、复用

  • 不直接面对前端或接口层

规范示例(你给出的 nb_tran_demo
sql 复制代码
CREATE PROCEDURE dbo.nb_tran_demo
AS
BEGIN
    SET NOCOUNT ON;
    SET IMPLICIT_TRANSACTIONS OFF;

    DECLARE @TranStarted BIT = 0;

    BEGIN TRY
        IF @@TRANCOUNT = 0
        BEGIN
            BEGIN TRAN;
            SET @TranStarted = 1;
        END

        -- ======================
        -- 业务逻辑
        -- ======================

        IF @TranStarted = 1
        BEGIN
            COMMIT;
        END
    END TRY
    BEGIN CATCH
        IF @TranStarted = 1 AND XACT_STATE() <> 0
        BEGIN
            ROLLBACK;
        END

        EXEC dbo.proc_error_log;

        THROW;
    END CATCH
END
设计要点
  • 必须使用 THROW

  • ❌ 禁止返回 SELECT code

  • ✅ 由上层决定事务命运


五、XACT_STATE 的强制使用规范

XACT_STATE() 含义 处理方式
1 事务可提交 可 COMMIT / ROLLBACK
-1 事务已损坏 只能 ROLLBACK
0 无事务 禁止操作

规范写法

sql 复制代码
IF @TranStarted = 1 AND XACT_STATE() <> 0 ROLLBACK; 

六、错误日志处理规范

统一错误记录过程(示意)

sql 复制代码
CREATE PROCEDURE dbo.proc_error_log
AS
BEGIN
    INSERT INTO sys_error_log (
        error_number,
        error_message,
        error_procedure,
        error_line,
        create_time
    )
    VALUES (
        ERROR_NUMBER(),
        ERROR_MESSAGE(),
        ERROR_PROCEDURE(),
        ERROR_LINE(),
        GETDATE()
    );
END

设计原则

  • ❌ 禁止在业务过程里散写 ERROR_*()

  • ✅ 所有异常集中入口

  • ✅ 支持后续告警、审计、分析


七、明确禁止的写法(红线)

❌ 在子存储过程中无条件 COMMIT

❌ CATCH 中继续执行业务 SQL

❌ 使用 @@ERROR

❌ 混用 RAISERRORTHROW

❌ 不区分接口型 / 业务型存储过程


八、工程级总结

一条铁律:谁开启事务,谁才有资格提交或回滚事务。

相关推荐
TDengine (老段)11 分钟前
TDengine Rust 连接器进阶指南
大数据·数据库·物联网·rust·时序数据库·tdengine·涛思数据
二哈喇子!16 分钟前
MySQL数据库操作命令【SQL语言】
数据库·sql·视图与索引
China_Yanhy18 分钟前
AWS S3 深度配置指南:每一栏每个选项有什么作用
java·数据库·aws
yong999022 分钟前
基于MATLAB的大变形悬臂梁求解程序
前端·数据库·matlab
施嘉伟24 分钟前
Oracle SQL Profile 固化执行计划实战说明
数据库·sql·oracle
Dr.Alex Wang29 分钟前
Google Firebase 实战教学 - Streamlit、Bucket、Firebase
数据库·python·安全·googlecloud
菜择贰36 分钟前
在linux(wayland)中禁用键盘
linux·运维·chrome
程序 代码狂人42 分钟前
SQL-速查表:NULL 相关函数对比
数据库·sql
oMcLin1 小时前
如何在 Manjaro Linux 上通过配置systemd服务管理,提升微服务架构的启动速度与资源效率
linux·微服务·架构
Kira Skyler1 小时前
bpftool -S 签名功能实现解析
linux