存储过程本身不慢,慢的是其内部sql未走索引、被锁住或触发磁盘临时表;常见原因包括参数类型不匹配、变量传参干扰优化器、函数包裹条件、游标低效遍历、临时表无索引、事务粒度失控及混合引擎风险。

存储过程本身不慢,慢的是它里面某条 SQL 被执行时没走索引、被锁住、或触发了磁盘临时表——效率低从来不是语法问题,而是执行上下文失控。
EXPLAIN 显示 type=ALL,但存储过程里明明建了索引
常见现象:单独跑 SELECT * FROM orders WHERE user_id = 123 很快,放进存储过程后变慢;EXPLAIN 一看 type 是 ALL,key 为 NULL。
- 参数类型不匹配:比如存储过程定义
IN p_user_id VARCHAR(32),而表字段是BIGINT,调用CALL proc('123')会隐式转成字符串,索引失效 - 变量传参干扰优化器:写成
WHERE user_id = in_user_id时,MySQL 5.7+ 无法准确估算选择率,常弃用索引;换成WHERE user_id = ?配合预编译调用才可靠 - 函数包裹条件:如
WHERE DATE(create_time) = '2026-06-01'或WHERE UPPER(name) = 'ABC',直接让索引失效
游标遍历万级数据,比客户端处理还慢
游标在 MySQL 中本质是单行驱动,每次 FETCH 都要维护结果集状态、加锁、分配内存——它不是“轻量循环”,而是 N 次独立查询的叠加。
- 游标体内嵌套
SELECT或UPDATE?实际是 N × M 次查询,比如扫 1 万行,每行查一次关联表,就是 1 万次网络往返 + 解析开销 - 游标结果集大且含排序/聚合?MySQL 可能被迫落盘到磁盘临时表,I/O 暴增
- 替代方案更稳:用
INSERT INTO tmp SELECT ...一次性落库;或改用JOIN/ 窗口函数(MySQL 8.0+)/ 客户端聚合
临时表没索引,后续 JOIN 全表扫描
CREATE TEMPORARY TABLE tmp AS (SELECT ...) 很常用,但默认无索引;如果之后对 tmp 做 WHERE 或 JOIN,就会全表扫。
- 临时表字段必须建索引:比如
CREATE TEMPORARY TABLE tmp (id BIGINT, status VARCHAR(20), INDEX idx_id_status (id, status)) - 别依赖
tmp_table_size:即使设得很大,一旦字段含TEXT或BLOB,仍会落磁盘;优先把关键过滤字段冗余为普通列 - 反复创建同名临时表?注意会话隔离,但频繁 DDL 本身有开销;可考虑复用或用普通表 + 前缀命名
事务粒度失控,锁等待拖垮并发
存储过程里包着 START TRANSACTION,但没控制好范围——一个长事务锁住整张 InnoDB 表,其他会话全卡住。
- 事务越短越好:只把真正需要原子性的语句包进
COMMIT,别把日志记录、通知调用、JSON 解析全塞进去 - 混合引擎风险:过程里同时操作
InnoDB和MyISAM表?ROLLBACK只回滚前者,后者已提交不可逆 - 高并发下慎用
SELECT ... FOR UPDATE:若没走索引,可能升级为表锁;确认EXPLAIN显示走了PRIMARY或唯一索引
真正卡住的往往不是“存储过程”这个容器,而是某条 SQL 在特定参数、特定统计信息、特定并发压力下的偶然失速——它可能因为一次未 ANALYZE TABLE、一个没覆盖的复合索引顺序、或一次意外的磁盘临时表,悄悄吃掉 90% 的时间。











