explain必须在存储过程外单独运行,因其无法展开分析内部sql;需将核心select/update等语句提取后执行explain,重点检查type、key、rows及extra字段,杜绝all扫描与using temporary等问题。

存储过程本身不慢,慢的是它里面没走索引的 SQL 或无意义返回的结果集。 优化重点不在“写法多漂亮”,而在让每条语句都命中索引、避免无效数据传输、控制上下文开销。
为什么 EXPLAIN 必须在存储过程外单独跑
直接对存储过程调用 EXPLAIN 是无效的——它只分析调用语句本身(比如 CALL sp_name()),不会展开内部 SQL。真正要查的是过程体里的每条关键语句。
- 把存储过程中核心的
SELECT、UPDATE、INSERT ... SELECT单独拎出来,在客户端或命令行里加EXPLAIN FORMAT=TREE执行 - 重点关注
type是否为range或更高(ref、const),杜绝ALL;key列是否非NULL;rows是否明显偏离实际扫描量 - 如果
extra出现Using temporary或Using filesort,说明ORDER BY、GROUP BY或去重逻辑没走索引,得补复合索引
SELECT 不加 INTO 就是性能黑洞
存储过程里写 SELECT col FROM t WHERE x = ? 却不接 INTO @var,MySQL 会强制构造结果集、分配缓冲区、触发网络协议层打包——哪怕你根本不要这结果。
- 所有中间查询必须明确目的:要取值就用
SELECT ... INTO;要写入就用INSERT ... SELECT;要判断存在性就用SELECT COUNT(*)+IF - 禁止出现裸
SELECT(即没有INTO、没被INSERT包裹、也没用在游标中) - 调试时临时加的
SELECT务必删掉,它们在生产环境会成倍放大内存与上下文切换开销
哪些字段该加索引?看 WHERE、JOIN、ORDER BY 里实际出现的列
不是“给主键加了索引就万事大吉”,而是每条执行计划里暴露的驱动列、过滤列、排序列,都要覆盖。
- 单列索引优先用于等值查询(
=、IN);复合索引按WHERE条件顺序排列,遵循最左匹配,例如WHERE a = ? AND b > ? ORDER BY c,适合建(a, b, c) -
JOIN字段类型必须严格一致(如都是INT或同为BIGINT),字符集也要相同,否则索引失效 - 避免冗余索引:
(a)和(a, b)同时存在时,前者通常可删;用SHOW INDEX FROM t检查Cardinality值过低的索引是否真有用
慢查询日志里真正要盯的不是“谁慢”,而是“为什么慢”
开启 slow_query_log 后,别只扫耗时 >1s 的语句;重点找那些在存储过程中高频出现、Rows_examined 远大于 Rows_sent 的记录——这是典型“查得多、用得少”。
- 配置
log_queries_not_using_indexes = ON,强制捕获所有未走索引的查询,包括存储过程内部语句 - 用
mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log聚合统计,看哪些SELECT模式反复出现 - 如果某条语句在日志里
Rows_examined是 50 万但Rows_sent是 1,基本可以断定:要么缺索引,要么写成了SELECT *+ 应用层再过滤
最容易被忽略的是:索引建了,但存储过程里用了函数包装字段,比如 WHERE YEAR(create_time) = 2024 —— 这会让整个索引失效。改用范围查询 WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31' 才能真正利用索引。











