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_deletesp_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;
-
事务开启规则:
@@TRANCOUNT = 0→ 开启事务@@TRANCOUNT > 0→ 复用外层事务
-
提交规则:
- 仅最外层事务允许
COMMIT
- 仅最外层事务允许
-
回滚规则:
- 仅最外层事务执行
ROLLBACK - 必须结合
XACT_STATE()判断
- 仅最外层事务执行
-
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 并发控制的基本原则(强制)
- 并发问题只能通过"写操作"解决,不能依赖只读校验
- 校验 + 修改必须处于同一事务中
- 能用 SQL 语义解决的,不使用应用层锁
- 优先使用"结果驱动式"并发判断,而不是"先查再改"
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 幂等性设计原则(强制)
- 所有可能被重复调用的业务接口必须设计幂等性
- 幂等性必须在数据库层兜底
- 不允许仅依赖前端或应用层缓存
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 最终强制性总结(公司级)
并发控制靠写语句,幂等控制靠唯一性,事务只包业务,异常即失败。
该章节与事务模板同级,作为公司数据库开发规范最终发布版的一部分。