SQL Server 存储过程开发规范(公司内部模板)

SQL Server 存储过程开发规范(公司内部模板)

一、适用范围

本规范适用于公司内部所有基于 SQL Server(2012+) 的存储过程开发,主要用于:

  • 核心业务数据处理
  • 强一致性事务操作
  • 需要与应用层(Spring / JPA / MyBatis 等)统一异常模型的场景

目标是:提升可维护性、降低沟通成本、统一异常与事务语义


二、总体设计原则

1. 职责分离

类型 职责 是否返回结果
校验型存储过程 参数 / 状态 / 权限校验 否(失败直接抛异常)
业务型存储过程 核心业务处理 否(成功无结果,失败异常)
查询型存储过程 数据查询 是(ResultSet)

2. 成功走流程,失败走异常

  • 成功路径:不返回 code / msg
  • 失败路径:统一通过异常(THROW)表达
  • 禁止在同一过程内混合使用 SELECT code,msg 与异常

三、错误码规范(强制)

1. 错误码区间

区间 含义
50000--50999 参数 / 数据校验错误
51000--51999 业务规则错误
52000--52999 权限 / 租户 / 状态错误
53000--53999 并发 / 状态冲突

SQL Server 自定义异常错误码 必须 ≥ 50000


2. 异常抛出标准写法

sql 复制代码
THROW 50001, N'无效单据id', 1;
  • 不允许使用 RAISERROR
  • 不允许使用 PRINT

四、命名规范

1. 存储过程命名

text 复制代码
sp_[模块]_[业务]_[动作]

示例:

  • sp_user_supplier_scope_delete
  • sp_order_confirm

2. 参数命名

  • 统一使用 @p_ 前缀
sql 复制代码
@p_id BIGINT
@p_tenant_id BIGINT

五、校验型存储过程模板(标准)

sql 复制代码
CREATE PROCEDURE sp_check_user_supplier_exists
    @p_id BIGINT
AS
BEGIN
    SET NOCOUNT ON;

    IF NOT EXISTS (
        SELECT 1
        FROM bs_tenant_user_supplier
        WHERE id = @p_id
    )
    BEGIN
        THROW 50001, N'无效单据id', 1;
    END
END

规范说明:

  • 不开启事务
  • 不返回任何结果集
  • 失败立即中断调用链

六、业务型存储过程模板(公司事务标准模板)

适用于所有包含 INSERT / UPDATE / DELETE 的业务型存储过程

sql 复制代码
CREATE PROCEDURE sp_xxx_business_action
    @p_id BIGINT
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON; -- 强制开启,防止运行时错误导致脏事务

    DECLARE @TranStarted BIT = 0;

    BEGIN TRY
        -- 仅在最外层开启事务,支持嵌套调用
        IF @@TRANCOUNT = 0
        BEGIN
            BEGIN TRAN;
            SET @TranStarted = 1;
        END

        -------------------------------------------------
        -- 1. 校验(只读校验,不开启事务)
        -------------------------------------------------
        EXEC sp_check_user_supplier_exists @p_id;

        -------------------------------------------------
        -- 2. 业务处理(必须在事务中)
        -------------------------------------------------
        DELETE FROM bs_tenant_user_supplier
        WHERE id = @p_id;

        -------------------------------------------------
        -- 3. 提交事务(仅最外层提交)
        -------------------------------------------------
        IF @TranStarted = 1
            COMMIT TRAN;
    END TRY
    BEGIN CATCH
        -------------------------------------------------
        -- 4. 异常处理(统一回滚 + 重新抛出)
        -------------------------------------------------
        IF @TranStarted = 1 AND XACT_STATE() <> 0
            ROLLBACK TRAN;

        -- 统一错误日志(不允许吞异常)
        EXEC dbo.bsyc_proc_error;

        -- 异常必须向上抛出,由应用层感知
        THROW;
    END CATCH
END

TRY

BEGIN CATCH

IF @@TRANCOUNT > 0

ROLLBACK TRAN;

复制代码
    THROW;
END CATCH

END

复制代码
---

## 七、事务处理规范(强制)

1. **凡是写数据的存储过程,必须使用事务模板**
2. 校验型存储过程 **禁止开启事务**
3. 必须使用 `TRY / CATCH`
4. 必须在过程开始处设置:

```sql
SET XACT_ABORT ON;
  1. 事务开启规则:

    • @@TRANCOUNT = 0 → 开启事务
    • @@TRANCOUNT > 0 → 复用外层事务
  2. 提交规则:

    • 仅最外层事务允许 COMMIT
  3. 回滚规则:

    • 仅最外层事务执行 ROLLBACK
    • 必须结合 XACT_STATE() 判断
  4. CATCH 中只允许:

    • 回滚事务
    • 记录错误日志
    • THROW; 原样抛出异常
      `

八、嵌套事务规范(进阶)

sql 复制代码
DECLARE @tranStarted BIT = 0;

IF @@TRANCOUNT = 0
BEGIN
    SET @tranStarted = 1;
    BEGIN TRAN;
END

BEGIN TRY
    -- 业务逻辑

    IF @tranStarted = 1
        COMMIT TRAN;
END TRY
BEGIN CATCH
    IF @tranStarted = 1 AND @@TRANCOUNT > 0
        ROLLBACK TRAN;
    THROW;
END CATCH

九、禁止事项(红线)

❌ 在 SP 中返回 code / msg 表示业务失败

❌ 在 CATCH 中吞掉异常

❌ 在一个事务中只包住部分业务逻辑

❌ 使用 RAISERROR

❌ 使用 @@ERROR 判断业务状态


十、应用层对接约定

应用层行为

  • 成功:无异常
  • 失败:捕获 SQLException

应用层可获取信息

字段 来源
errorCode THROW 第一个参数
message THROW 第二个参数

十一、模板总结(强制执行)

校验即异常,异常即失败,事务只在主流程,错误只抛不吞。

该模板作为公司级统一规范,所有新建及重构存储过程必须遵循。


十二、并发与幂等控制(最终发布版)

12.1 设计目标

本章节用于规范 高并发场景下的数据一致性与接口幂等性,适用于:

  • 重试机制(网络抖动、超时重试)
  • 分布式调用
  • 人工重复操作
  • 并发更新同一业务对象

目标是:避免重复执行、避免脏写、避免状态回退


12.2 并发控制的基本原则(强制)

  1. 并发问题只能通过"写操作"解决,不能依赖只读校验
  2. 校验 + 修改必须处于同一事务中
  3. 能用 SQL 语义解决的,不使用应用层锁
  4. 优先使用"结果驱动式"并发判断,而不是"先查再改"

12.3 推荐并发控制模式一:基于影响行数(首选)

适用场景

  • DELETE / UPDATE
  • 状态流转
  • 乐观并发控制

标准模板

sql 复制代码
UPDATE t_order
SET status = 'CONFIRMED'
WHERE id = @p_id
  AND status = 'INIT';

IF @@ROWCOUNT = 0
BEGIN
    THROW 53001, N'订单状态已变更或不存在', 1;
END

优势:

  • 单条语句完成并发控制
  • 天然线程安全
  • 无需额外锁

12.4 推荐并发控制模式二:版本号(乐观锁)

表结构要求

sql 复制代码
version INT NOT NULL

模板

sql 复制代码
UPDATE t_xxx
SET col = @val,
    version = version + 1
WHERE id = @p_id
  AND version = @p_version;

IF @@ROWCOUNT = 0
BEGIN
    THROW 53002, N'数据已被其他事务修改', 1;
END

12.5 推荐并发控制模式三:必要时行级锁

使用场景(谨慎)

  • 资金
  • 库存
  • 强顺序依赖
sql 复制代码
SELECT 1
FROM t_stock WITH (UPDLOCK, ROWLOCK)
WHERE id = @p_id;

说明:仅在事务中使用,禁止滥用。


12.6 幂等性设计原则(强制)

  1. 所有可能被重复调用的业务接口必须设计幂等性
  2. 幂等性必须在数据库层兜底
  3. 不允许仅依赖前端或应用层缓存

12.7 幂等控制模式一:业务唯一键(首选)

表结构示例

sql 复制代码
request_no VARCHAR(64) NOT NULL UNIQUE

插入模板

sql 复制代码
INSERT INTO t_xxx(request_no, col1, col2)
VALUES (@p_request_no, @v1, @v2);
  • 重复请求 → 唯一键异常
  • 自动进入事务 CATCH

12.8 幂等控制模式二:状态机驱动

示例

sql 复制代码
UPDATE t_order
SET status = 'PAID'
WHERE id = @p_id
  AND status = 'UNPAID';

IF @@ROWCOUNT = 0
BEGIN
    THROW 51001, N'订单已支付或状态非法', 1;
END

12.9 禁止的并发 / 幂等写法(红线)

❌ 先 SELECT 校验,再 UPDATE / DELETE

❌ 在事务外判断状态

❌ 使用应用层分布式锁替代数据库一致性

❌ 忽略 @@ROWCOUNT 的写操作


12.10 最终强制性总结(公司级)

并发控制靠写语句,幂等控制靠唯一性,事务只包业务,异常即失败。

该章节与事务模板同级,作为公司数据库开发规范最终发布版的一部分。

相关推荐
zgl_200537791 小时前
ZGLanguage 解析SQL数据血缘 之 Python + Echarts 显示SQL结构图
大数据·数据库·数据仓库·hadoop·sql·代码规范·源代码管理
行百里er2 小时前
用 ThreadLocal + Deque 打造一个“线程专属的调用栈” —— Spring Insight 的上下文管理术
java·后端·架构
acaad2 小时前
Redis下载与安装(Windows)
数据库·redis·缓存
玄〤2 小时前
黑马点评中 VoucherOrderServiceImpl 实现类中的一人一单实现解析(单机部署)
java·数据库·redis·笔记·后端·mybatis·springboot
zz_nj2 小时前
工作的环境
linux·运维·服务器
SunflowerCoder2 小时前
EF Core + PostgreSQL 配置表设计踩坑记录:从 23505 到 ChangeTracker 冲突
数据库·postgresql·c#·efcore
J_liaty2 小时前
Spring Boot拦截器与过滤器深度解析
java·spring boot·后端·interceptor·filter
短剑重铸之日2 小时前
《7天学会Redis》Day2 - 深入Redis数据结构与底层实现
数据结构·数据库·redis·后端
亲爱的非洲野猪3 小时前
Java锁机制八股文
java·开发语言