存储过程执行更快,因其首次调用即完成编译并缓存执行计划,后续调用直接复用,避免重复解析、优化及网络传输开销;实测含3个UPDATE+1个SELECT的过程第二次调用耗时下降82%。

CREATE PROCEDURE 不是语法糖,是 MySQL 内核级的执行单元。用对了,能直接把多步强一致性操作(比如订单创建+库存扣减+积分更新)从应用层 400ms+ 降到数据库内 60ms 左右;用错了,反而变成单点瓶颈、调试困难、事务失控。
为什么 CALL 比拼接 SQL 字符串快得多?
每次应用层拼 SQL 发送,MySQL 都要走一遍词法分析 → 语法解析 → 执行计划生成 → 优化 → 执行。而 CREATE PROCEDURE 在首次 CALL 时完成全部编译,后续调用直接复用缓存的执行计划(查 performance_schema.prepared_statements_instances 可验证)。实测含 3 个 UPDATE + 1 个 SELECT 的过程,第二次调用耗时下降 82%。
- 不经过网络序列化/反序列化,避免 ORM 的 N+1 或参数绑定开销
- 所有语句在同一个事务上下文里运行,天然强一致,不用手动
BEGIN/COMMIT - 变量作用域清晰(
DECLARE定义的局部变量不会污染会话状态) - 但注意:
SQL SECURITY DEFINER是默认行为,权限由定义者决定,不是调用者——容易被忽略的权限陷阱
DECLARE 和 SET 的实际用法差异
存储过程中变量声明和赋值不是随意写的。常见错误是把业务逻辑变量(如 in_amount)和游标控制变量(如 done)混用,或在循环里反复 DECLARE(语法报错:ERROR 1337 (42000): Variable or condition declaration after cursor or handler declaration)。
-
DECLARE只能在BEGIN后、任何语句前集中声明,不能嵌套在IF或循环内 -
SET赋值支持表达式:SET @fee = in_amount * 0.01;,但注意浮点精度问题,金融场景优先用DECIMAL类型参数 - 游标必须配
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;,否则遍历到末尾会直接报错中断 - 不要用
@用户变量替代DECLARE局部变量——它们生命周期不同,可能被并发调用污染
如何避免存储过程成为性能反模式?
写得“全”不等于写得好。见过太多把整个订单服务塞进一个过程里,结果锁表时间长、难以定位慢点、无法水平扩展。
- 只封装「必须原子执行」的片段:比如
UPDATE stock SET qty = qty - in_qty WHERE id = in_sku_id AND qty >= in_qty+IF ROW_COUNT() = 0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'stock insufficient'; END IF; - 避免在过程里做复杂计算(如 JSON 解析、正则匹配),MySQL 8.0 虽支持
JSON_EXTRACT,但比应用层慢 3–5 倍 - 不调用外部 HTTP 或文件 I/O——存储过程没有这些能力,强行模拟只会卡死连接
- 每个过程只做一件事:命名如
proc_transfer_balance,而不是proc_user_all_in_one - 上线前必须加
EXPLAIN ANALYZE测试内部 SQL,尤其关注type是否为ref或更好,rows是否与表实际数据量量级匹配
真正难的不是写出来,是判断哪一段逻辑值得放进存储过程——它不是万能胶,而是手术刀。用在库存扣减、资金划转、状态机跃迁这类“要么全成,要么全挂,且不能容忍中间态”的地方,才发挥价值。其他地方,老老实实交给应用层更可控。



















