MySQL 存储过程编码规范
适用范围:ERP / SaaS / 单据型业务,基于 MySQL 8+ ,面向写事务型存储过程(保存、审核、反审核、作废、批处理等)。
本规范统一采用:
- LEAVE 模式
- 独立调用的业务存储过程:直接
SELECT code、msg返回- 子存储过程:统一
OUT out_code / OUT out_msg- 业务主存储过程可按业务返回额外字段
- 复杂单据支持 JSON 入参
- 适配主表 + 明细表 + 状态流转 + 库存联动场景
1. 适用范围
本规范适用于以下类型的 MySQL 存储过程:
-
单据保存类
- 销售单保存
- 采购单保存
- 入库单 / 出库单保存
- 调拨单保存
- 消息发送
-
状态流转类
- 审核
- 反审核
- 作废
- 关闭
- 完成
-
批处理类
- 批量生成消息
- 批量结转积分
- 批量更新状态
- 日 / 月汇总
-
简单查询类
- 原则上不推荐用存储过程做普通列表查询
- 查询优先走 SQL / Mapper / Repository
- 存储过程主要承担写事务与批处理
2. 设计原则
2.1 一个存储过程只负责一个明确业务动作
例如:
sp_sale_order_save:保存销售单sp_sale_order_audit:审核销售单sp_sale_order_cancel:作废销售单sp_msg_send:发送消息sp_member_point_expire:积分失效处理
不要把多个业务动作塞进一个过程,例如:
- 保存 + 审核 + 发货 + 关闭 全放一个过程
2.2 存储过程负责"数据库事务逻辑",不负责页面逻辑
适合放在存储过程中的内容
- 参数校验
- 数据存在性校验
- 单据状态校验
- 主表保存
- 明细新增 / 修改 / 删除
- 金额重算
- 库存调整
- 状态流转
- 批量写库
- 事务控制
- 错误回滚
不适合放在存储过程中的内容
- 页面展示逻辑
- UI 交互逻辑
- 权限菜单逻辑
- 调外部 HTTP 接口
- 调消息中间件
- 调其他微服务
- 跨系统编排
2.3 过程内部优先使用集合操作,不要写游标式逐行逻辑
优先使用:
INSERT ... SELECTUPDATE ... JOINDELETE ... JOINJSON_TABLE- 临时表
- 中间差异表
尽量避免:
- 游标逐行处理
WHILE一行行循环更新- 用字符串拆分模拟数组
2.4 写操作过程必须可回滚、可返回明确状态
所有写操作过程都必须:
- 成功时返回
code = 200 - 失败时返回
code = 500 - 出错时回滚
- 成功时明确提交
3. 命名规范
3.1 存储过程命名
统一前缀
存储过程统一使用前缀:
sql
sp_
命名结构
建议格式:
sql
sp_业务对象_动作
例如:
sp_sale_order_savesp_sale_order_auditsp_sale_order_cancelsp_purchase_order_savesp_stock_in_savesp_stock_out_auditsp_msg_send
3.2 参数命名
输入参数统一前缀:in_
sql
IN in_id BIGINT,
IN in_user_id BIGINT,
IN in_shop_id BIGINT,
IN in_data_json JSON
子过程输出参数统一前缀:out_
sql
OUT out_code INT,
OUT out_msg VARCHAR(200)
注意:这里只针对子存储过程 。
独立调用的业务存储过程不使用
OUT out_code/out_msg返回状态。
3.3 局部变量命名
局部变量统一前缀:
sql
v_
例如:
sql
DECLARE v_count INT DEFAULT 0;
DECLARE v_bill_id BIGINT DEFAULT 0;
DECLARE v_bill_no VARCHAR(50) DEFAULT '';
DECLARE v_total_num DECIMAL(18,2) DEFAULT 0;
DECLARE v_total_amount DECIMAL(18,2) DEFAULT 0;
DECLARE v_now DATETIME DEFAULT NOW();
3.4 临时表命名
临时表统一使用:
sql
tmp_
例如:
tmp_order_itemtmp_order_item_difftmp_msg_usertmp_stock_adjust
3.5 标签命名
统一使用:
sql
proc_label:
例如:
sql
proc_label:BEGIN
...
LEAVE proc_label;
END
4. 返回规范
4.1 返回分层原则
存储过程返回规范分为两层:
-
独立调用的业务存储过程
- 供前端 / Java / 程序层直接调用
- 不使用 OUT 返回状态
- 在过程末尾直接
SELECT code、msg返回 - 如有业务结果,可一并
SELECT返回
-
子存储过程
- 供主存储过程内部调用
- 必须统一返回
out_code、out_msg
4.2 独立调用的业务存储过程返回规范
4.2.1 适用范围
独立调用的业务存储过程是指:
- 前端直接调用
- Java 服务直接调用
- 作为一个完整业务动作入口调用
例如:
sp_msg_readsp_msg_sendsp_sale_order_savesp_sale_order_auditsp_sale_order_cancel
4.2.2 返回方式
独立调用的业务存储过程,统一通过 结果集 返回状态:
sql
SELECT 200 AS code, '成功' AS msg;
失败时:
sql
SELECT 500 AS code, '失败原因' AS msg;
4.2.3 可扩展返回字段
如果过程还需要返回业务结果,应直接和 code/msg 一起返回,例如:
sql
SELECT
200 AS code,
'成功' AS msg,
v_id AS id,
v_bill_no AS billNo,
v_status AS status;
常见业务返回字段
idbillNostatustotalAmounttotalNum
4.2.4 统一要求
成功返回
sql
SELECT 200 AS code, '成功' AS msg;
失败返回
sql
SELECT 500 AS code, '失败原因' AS msg;
禁止写法
独立调用的业务存储过程中,不要使用:
sql
SET out_code = 200;
SET out_msg = '成功';
也不要定义:
sql
OUT out_code INT,
OUT out_msg VARCHAR(200)
4.3 子存储过程返回规范
4.3.1 适用范围
子存储过程是供其他存储过程内部调用的过程,例如:
- 明细同步过程
- 汇总重算过程
- 库存处理过程
- 状态校验过程
- 日志写入过程
- 编号生成过程
- 数据校验过程
例如:
sp_sale_order_item_syncsp_sale_order_recalcsp_sale_order_stock_applysp_stock_validatesp_bill_no_generate
4.3.2 必须返回字段
所有子存储过程统一要求:
sql
OUT out_code INT,
OUT out_msg VARCHAR(200)
4.3.3 返回规则
成功
sql
SET out_code = 200;
SET out_msg = '成功';
失败
sql
SET out_code = 500;
SET out_msg = '具体失败原因';
例如:
'库存不足''单据明细不合法''商品不存在''当前状态不允许操作'
4.4 状态码约定
默认基础约定:
| 状态码 | 含义 |
|---|---|
| 200 | 成功 |
| 500 | 失败 |
如果业务需要细分,建议扩展为:
| 状态码 | 含义 |
|---|---|
| 200 | 成功 |
| 400 | 参数错误 |
| 401 | 数据不存在 |
| 402 | 状态不允许 |
| 403 | 权限 / 归属不允许 |
| 409 | 数据冲突 / 重复 |
| 500 | 系统异常 |
如果没有特殊要求,默认先用 200 / 500。
5. 过程结构规范
5.1 独立调用业务过程标准骨架
sql
CREATE PROCEDURE sp_xxx(
IN in_xxx ...
)
proc_label:BEGIN
DECLARE v_now DATETIME DEFAULT NOW();
DECLARE v_error_text TEXT;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1 v_error_text = MESSAGE_TEXT;
ROLLBACK;
SELECT 500 AS code, CONCAT('系统异常:', v_error_text) AS msg;
END;
START TRANSACTION;
-- 1. 参数校验
-- 2. 数据校验
-- 3. 业务处理
COMMIT;
SELECT 200 AS code, '成功' AS msg;
END;
5.2 子存储过程标准骨架
sql
CREATE PROCEDURE sp_xxx_sub(
IN in_xxx ...,
OUT out_code INT,
OUT out_msg VARCHAR(200)
)
proc_label:BEGIN
DECLARE v_error_text TEXT;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1 v_error_text = MESSAGE_TEXT;
SET out_code = 500;
SET out_msg = CONCAT('系统异常:', v_error_text);
END;
SET out_code = 200;
SET out_msg = '成功';
-- 子过程业务逻辑
END;
5.3 统一使用 LEAVE 模式提前退出
禁止在过程里到处嵌套多层 IF ... ELSE ...。校验失败时,直接:
独立业务过程
sql
ROLLBACK;
SELECT 500 AS code, 'xxx' AS msg;
LEAVE proc_label;
子过程
sql
SET out_code = 500;
SET out_msg = 'xxx';
LEAVE proc_label;
6. 主过程调用子过程规范
6.1 主过程调用子过程必须检查返回值
主过程调用子过程时,必须接收并判断子过程返回的 out_code、out_msg。
禁止只调用不判断结果。
错误示例
sql
CALL sp_sale_order_item_sync(...);
CALL sp_sale_order_recalc(...);
CALL sp_sale_order_stock_apply(...);
6.2 正确调用模板
sql
CALL sp_sale_order_item_sync(v_order_id, in_data_json, v_sub_code, v_sub_msg);
IF v_sub_code <> 200 THEN
ROLLBACK;
SELECT v_sub_code AS code, v_sub_msg AS msg;
LEAVE proc_label;
END IF;
CALL sp_sale_order_recalc(v_order_id, v_sub_code, v_sub_msg);
IF v_sub_code <> 200 THEN
ROLLBACK;
SELECT v_sub_code AS code, v_sub_msg AS msg;
LEAVE proc_label;
END IF;
6.3 主过程定义子过程接收变量
建议统一定义:
sql
DECLARE v_sub_code INT DEFAULT 200;
DECLARE v_sub_msg VARCHAR(200) DEFAULT '成功';
7. 参数规范
7.1 单值参数
简单过程直接传普通参数:
sql
IN in_id BIGINT,
IN in_user_id BIGINT
例如:
- 阅读消息
- 审核单据
- 删除草稿
7.2 复杂单据保存统一传 JSON
对于"主表 + 明细表"的保存类过程,建议统一传:
sql
IN in_data_json JSON
而不是把每个字段都展开成几十个参数。
7.3 JSON 结构建议
统一采用:
json
{
"bill": {
"id": 1,
"billNo": "SO202606210001",
"remark": "测试"
},
"items": [
{
"id": 11,
"skuId": 101,
"num": 2,
"price": 100
}
],
"ext": {
"source": "pc"
}
}
建议分成三层:
bill:主表字段items:明细列表ext:扩展字段
8. 校验规范
8.1 参数校验放在最前面
先校验入参是否为空、格式是否合理,再做数据库操作。
例如独立过程:
sql
IF in_user_id IS NULL OR in_user_id <= 0 THEN
ROLLBACK;
SELECT 500 AS code, '用户不能为空' AS msg;
LEAVE proc_label;
END IF;
例如子过程:
sql
IF in_user_id IS NULL OR in_user_id <= 0 THEN
SET out_code = 500;
SET out_msg = '用户不能为空';
LEAVE proc_label;
END IF;
8.2 存在性校验要显式写
例如:
sql
SELECT COUNT(1)
INTO v_count
FROM sale_order
WHERE id = in_id;
IF v_count = 0 THEN
ROLLBACK;
SELECT 500 AS code, '单据不存在' AS msg;
LEAVE proc_label;
END IF;
8.3 状态校验必须明确
例如审核前要校验:
- 是否已删除
- 是否已审核
- 是否允许当前状态流转
9. JSON 明细处理规范
9.1 明细统一落临时表
对于单据保存类过程,先把 JSON 明细转成临时表:
sql
CREATE TEMPORARY TABLE tmp_order_item(
id BIGINT,
sku_id BIGINT,
num DECIMAL(18,2),
price DECIMAL(18,2)
);
然后:
sql
INSERT INTO tmp_order_item(id, sku_id, num, price)
SELECT
jt.id,
jt.sku_id,
jt.num,
jt.price
FROM JSON_TABLE(
JSON_EXTRACT(in_data_json, '$.items'),
'$[*]' COLUMNS(
id BIGINT PATH '$.id' DEFAULT NULL ON EMPTY,
sku_id BIGINT PATH '$.skuId',
num DECIMAL(18,2) PATH '$.num',
price DECIMAL(18,2) PATH '$.price'
)
) jt;
10. 明细同步规范(新增 / 修改 / 删除)
10.1 保存类单据必须支持明细差异同步
对于修改单据,不要先全删后全插,优先做:
- 新增
- 修改
- 删除
10.2 建议做法:新旧集合比对
新明细
- 来自
tmp_order_item
旧明细
- 来自正式表
sale_order_item
然后分三类处理:
- 新有旧无:新增
- 新旧都有但字段变化:修改
- 旧有新无:删除
11. 审核 / 反审核 / 作废规范
11.1 每个动作独立过程
例如:
sp_sale_order_auditsp_sale_order_unauditsp_sale_order_cancel
不要把这些动作混在保存过程里通过 actionType 分支处理。
11.2 审核动作必须做状态校验
例如:
- 草稿才能审核
- 审核后不能重复审核
- 作废单不能审核
11.3 审核动作要明确副作用
审核可能伴随:
- 扣减库存
- 生成出库任务
- 写财务流水
- 写操作日志
这些都必须在同一事务内完成。
12. 日志规范
12.1 关键业务动作要写日志
例如:
- 保存单据
- 提交审核
- 审核通过
- 反审核
- 作废
12.2 日志写入也放在事务里
这样能保证:
- 单据保存成功,日志也成功
- 单据回滚,日志也不留脏数据
13. SQL 编写规范
13.1 关键字统一大写
例如:
SELECTUPDATEDELETEFROMWHEREJOINIFBEGINEND
13.2 不要写 SELECT *
过程里统一显式列出字段,便于维护和避免字段变更影响。
14. 事务规范
14.1 一个业务动作一个事务
例如:
- 保存单据 = 一个事务
- 审核单据 = 一个事务
- 作废单据 = 一个事务
14.2 校验失败要显式回滚并 LEAVE
独立业务过程
sql
ROLLBACK;
SELECT 500 AS code, 'xxx' AS msg;
LEAVE proc_label;
子过程
sql
SET out_code = 500;
SET out_msg = 'xxx';
LEAVE proc_label;
14.3 成功只在末尾 COMMIT
避免在中间分支里随意 COMMIT。
15. 独立调用业务过程模板
sql
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_xxx$$
CREATE PROCEDURE sp_xxx(
IN in_id BIGINT,
IN in_user_id BIGINT
)
proc_label:BEGIN
DECLARE v_now DATETIME DEFAULT NOW();
DECLARE v_count INT DEFAULT 0;
DECLARE v_error_text TEXT;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1 v_error_text = MESSAGE_TEXT;
ROLLBACK;
SELECT 500 AS code, CONCAT('系统异常:', v_error_text) AS msg;
END;
START TRANSACTION;
IF in_user_id IS NULL OR in_user_id <= 0 THEN
ROLLBACK;
SELECT 500 AS code, '用户不能为空' AS msg;
LEAVE proc_label;
END IF;
-- 业务逻辑
COMMIT;
SELECT 200 AS code, '成功' AS msg;
END$$
DELIMITER ;
16. 子存储过程模板
sql
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_xxx_sub$$
CREATE PROCEDURE sp_xxx_sub(
IN in_xxx BIGINT,
OUT out_code INT,
OUT out_msg VARCHAR(200)
)
proc_label:BEGIN
DECLARE v_error_text TEXT;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1 v_error_text = MESSAGE_TEXT;
SET out_code = 500;
SET out_msg = CONCAT('系统异常:', v_error_text);
END;
SET out_code = 200;
SET out_msg = '成功';
-- 子过程业务逻辑
IF in_xxx IS NULL OR in_xxx <= 0 THEN
SET out_code = 500;
SET out_msg = '参数不能为空';
LEAVE proc_label;
END IF;
END$$
DELIMITER ;
17. 项目中建议固定的统一规则
- MySQL 存储过程全部使用
LEAVE模式 - 独立调用的业务存储过程统一
SELECT code,msg返回 - 子存储过程统一返回
OUT out_code / OUT out_msg - 保存类业务过程返回
code/msg + id/billNo - 状态流转类过程可返回
code/msg + status - 输入参数统一
in_前缀 - 局部变量统一
v_前缀 - 临时表统一
tmp_前缀 - 过程统一使用
proc_label:BEGIN ... LEAVE proc_label - 复杂单据统一
in_data_json JSON入参 - 前端不可信任汇总金额,数据库重算
- 修改单据时,明细按"新增 / 修改 / 删除"同步,不要简单全删全插
- 所有审核 / 反审核 / 作废动作单独一个过程
- 主过程调用子过程时必须检查子过程返回值
- 异常统一
EXIT HANDLER + ROLLBACK/SET out_code + out_msg
18. 返回规范一句话总结
独立调用业务过程
- 直接
SELECT code,msg,...返回给前端 / Java - 不使用 OUT 返回状态
子存储过程
- 统一使用
OUT out_code / OUT out_msg - 供主过程内部调用和判断结果