慢查询日志只记录call语句而不展开存储过程内部sql,需通过rows_examined与rows_sent对比、单独explain过程内sql、确保参数类型与字段一致、避免裸select等手段定位和优化性能瓶颈。

慢查询日志里只记 CALL,不记内部 SQL
直接查 slow_query_log 文件,你只会看到类似 CALL proc_user_report(202608, 'active') 这样的记录,耗时 1.8s —— 但日志**完全不展开**过程体内的任何语句。这不是 bug,是设计如此:MySQL 把存储过程调用当作一条独立语句处理,内部执行逻辑不在日志上下文中暴露。
所以别指望靠 grep CALL 找出哪条 SELECT 拖垮了性能。真正要盯的是这条记录的 Rows_examined 值:如果它远大于 Rows_sent(比如 50 万 vs 10),基本说明过程里有全表扫描或低效 JOIN。
- 开启日志前先确认配置:
SET GLOBAL slow_query_log = ON;、SET GLOBAL long_query_time = 0.5; - 强烈建议同时开
SET GLOBAL log_queries_not_using_indexes = ON;——哪怕语句没超时,只要没走索引就记,这对定位存储过程内“安静却致命”的语句特别有用 - 日志路径用
SHOW VARIABLES LIKE 'slow_query_log_file';确认,确保 MySQL 用户对该路径有写权限
EXPLAIN 必须脱离存储过程单独跑
EXPLAIN CALL proc_name(...) 没有意义,它只分析调用动作本身,输出永远是 type: SIMPLE、key: NULL。真正要优化的,是过程体里那些 SELECT、UPDATE、INSERT ... SELECT 语句。
操作步骤很直接:打开过程定义(SHOW CREATE PROCEDURE proc_name;),把疑似慢的 SQL 拿出来,**手动代入真实参数值**再跑 EXPLAIN FORMAT=TREE。例如过程里有 WHERE user_id = p_user_id,就换成 WHERE user_id = 12345。
- 绝对不要用变量形式
WHERE id = @var或WHERE id = p_id做 EXPLAIN——优化器无法估算选择率,rows字段会严重失真 - 重点看三个字段:
type(不是ALL或index)、key(是否命中你建的索引)、rows(是否接近实际匹配行数) - 如果
Extra出现Using temporary或Using filesort,说明ORDER BY或GROUP BY没走索引,得补复合索引
存储过程里裸 SELECT 是隐形炸弹
写 SELECT status, count FROM t_log WHERE day = p_day; 却没接 INTO @v_status, @v_count,MySQL 会强制构造完整结果集、分配网络缓冲区、触发协议层打包——哪怕你根本不需要返回数据。这在循环里尤其危险,每执行一次都放大内存和上下文开销。
所有中间查询必须明确归宿:
- 要取值 → 用
SELECT col INTO var_name - 要写入临时表 → 用
INSERT INTO tmp SELECT ... - 只判断存在性 → 改成
SELECT COUNT(*) > 0+IF - 调试用的
SELECT *必须上线前删光,它们在高并发下会迅速吃光连接内存
参数类型不一致直接让索引失效
过程定义 IN p_user_id VARCHAR(32),但表字段是 BIGINT,调用 CALL proc(123) 时,MySQL 会把字段隐式转成字符串再比对,索引立刻失效。同理,CHARSET 不同(如 utf8mb4 vs utf8)、函数包裹(WHERE DATE(created_at) = '2026-08-10')也会断掉索引链。
检查方法很简单:把过程里那句 SQL 拿出来,用 EXPLAIN 看 key 是否为 NULL,再对比 SHOW CREATE TABLE 里的字段定义和过程参数声明。
- 修复方案:参数类型严格对齐字段类型,宁可改过程定义,也不要依赖隐式转换
- 字符字段优先用
VARCHAR而非TEXT作参数,后者容易触发全表扫描 - 避免在 WHERE 条件里对字段做函数运算,把逻辑移到应用层或改用范围查询
SELECT、一个被忽略的隐式转换、或者调试时忘记删的裸 SELECT。日志和 EXPLAIN 都只是镜子,照出问题,但动手改 SQL 和参数才是关键。











