存储过程应专注原子数据操作与简单控制流,避免业务逻辑;参数用p_、变量用v_、游标用c_前缀;错误处理须用exit handler+rollback+resignal;游标遍历需显式检查not found。

存储过程里别写太多业务逻辑
MySQL 存储过程不是应用层,硬塞订单校验、状态机跳转、HTTP 调用封装(哪怕只是模拟)进去,只会让调试变慢、测试失效、部署耦合。它该干的事很窄:原子性数据操作 + 简单条件分支 + 结果集组装。
-
SELECT/INSERT/UPDATE/DELETE是主力,CASE和IF控制流向即可,别写嵌套 5 层的IF - 需要调外部服务?交给应用层——存储过程里用
SLEEP()或GET_LOCK()模拟等待,是权宜之计,不是设计 - 遇到“这个逻辑放哪儿都别扭”,大概率是拆分粒度不够:把校验抽成函数(
DELIMITER $$ CREATE FUNCTION ...),把主流程拆成多个小存储过程,用应用协调
参数命名必须带前缀避免冲突
MySQL 存储过程参数和局部变量同域,不加区分会覆盖、静默出错。比如参数叫 user_id,你又声明 DECLARE user_id INT DEFAULT 0;,后面所有对 user_id 的读写都指向局部变量,传进来的值彻底丢失。
- 统一用
p_前缀标识参数:p_user_id、p_status - 用
v_前缀标识局部变量:v_count、v_max_retry - 游标变量也遵守:
c_order_cursor,而非cursor - 这个习惯在
SHOW CREATE PROCEDURE查看定义时特别有用,一眼能分清作用域
错误处理不能只靠 CONTINUE HANDLER
CONTINUE HANDLER FOR SQLEXCEPTION 捕获后若不做显式 ROLLBACK 或日志记录,事务可能半途卡住,后续语句继续执行,结果不可预测。更糟的是,它还会吞掉原错误码,让调用方只收到模糊的 1329 No data - zero rows fetched。
- 每个需事务保障的过程开头加
DECLARE EXIT HANDLER FOR SQLEXCEPTION,里面做ROLLBACK+RESIGNAL(MySQL 5.7+)或拼接GET DIAGNOSTICS重抛 - 不要在
CONTINUEhandler 里尝试“修复”错误,比如捕获主键冲突就改 ID——这掩盖了并发问题本质 - 对非关键查询(如统计补全),可用
DECLARE CONTINUE HANDLER FOR SQLWARNING忽略警告,但NOT FOUND和SQLEXCEPTION必须明确响应
游标遍历必须检查 NOT FOUND 状态
MySQL 游标没有自动 EOF 判断,FETCH 到末尾后,变量值不会清空也不会报错,而是保持上一次值——导致循环多跑一次、数据重复处理、甚至死循环。
- 声明布尔变量:
DECLARE v_done BOOLEAN DEFAULT FALSE; - 定义 handler:
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE; - 循环中先
FETCH,再用IF v_done THEN LEAVE loop_label; END IF;判断退出 - 别依赖
ROW_COUNT()判断游标是否结束——它返回的是上一条语句影响行数,和游标无关
复杂点在于:存储过程没法像应用代码那样打堆栈、设断点。每次修改都要 DROP PROCEDURE + CREATE,而 SHOW WARNINGS 又只显示最近一条。真要定位问题,得靠提前埋好的 SELECT CONCAT('debug:', v_var) 输出,或者用 information_schema.PROCESSLIST 查正在运行的会话状态。











