查询没走索引是最常见的慢查询原因,如左模糊匹配、字段参与计算、排序字段未包含在联合索引中等均会导致索引失效;应使用EXPLAIN分析执行计划,避免函数操作索引列,合理设置事务边界并结合slow_log综合判断。

查询没走索引,是最常见的慢原因
MySQL 会默认对 WHERE、ORDER BY、GROUP BY 字段尝试用索引加速,但稍不注意就失效。比如:WHERE name LIKE '%张%'(左模糊)、WHERE status + 1 = 2(字段参与计算)、WHERE name = ? AND age > ? ORDER BY created_at 但索引是 (name, age) 而没包含 created_at —— 这些都会导致排序或条件部分无法复用索引。
实操建议:
- 用
EXPLAIN看type是否为ALL或index(全表/全索引扫描),key列是否为NULL - 避免在索引列上做函数操作,如
WHERE DATE(create_time) = '2024-01-01'→ 改成WHERE create_time >= '2024-01-01' AND create_time - 联合索引注意最左前缀原则,查询条件跳过首字段(如索引
(a,b,c),只查b = ?)就完全用不上
大表 JOIN 或子查询没加驱动表控制
MySQL 的 JOIN 是嵌套循环实现,驱动表(外层表)越小,整体扫描行数越少。如果优化器选错驱动表,比如拿千万级用户表去驱动几十万订单表,就会产生亿级中间结果。
实操建议:
- 用
STRAIGHT_JOIN强制指定驱动表顺序,例如SELECT ... FROM small_table STRAIGHT_JOIN big_table ON ... - 子查询尽量转成
JOIN,特别是IN (SELECT ...)类型,MySQL 5.6+ 虽有优化,但复杂条件下仍可能退化为 N+1 查询 - 检查
EXPLAIN输出的rows列,两表rows相乘若远超预期结果集大小,大概率是驱动顺序或连接条件缺失索引
sort_buffer_size 和 join_buffer_size 设置过小
当 ORDER BY 或 JOIN 无法走索引完成排序/关联时,MySQL 会把数据读进内存 buffer 做归并或哈希。如果 buffer 不够,就会写临时文件到磁盘(Using filesort / Using temporary),I/O 开销剧增。
实操建议:
- 观察
SHOW STATUS LIKE 'Sort_merge_passes',值持续增长说明频繁落盘排序;Created_tmp_disk_tables高则代表磁盘临时表多 - 临时调大会话级变量:
SET sort_buffer_size = 4M;(注意不是全局改,避免内存耗尽) - 该类 buffer 是「每个查询独占」而非共用,设太大反而引发内存争抢,一般单次查询 2–8M 足够,勿盲目堆到 256M
锁等待和 MVCC 版本链过长
看似只读查询变慢,很可能是被写事务阻塞。InnoDB 的一致性读需遍历版本链(rollptr 指向 undo log),若长期运行的事务未提交,后续所有快照读都要回溯更长的链,CPU 消耗明显上升。
实操建议:
- 查阻塞源:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW()) - TIME_TO_SEC(TRX_STARTED) > 60;找出运行超 1 分钟的事务 - 配合
information_schema.INNODB_LOCK_WAITS和INNODB_LOCKS(MySQL 5.7+ 已移除,改用performance_schema.data_locks)定位谁在等谁 - 业务中避免在事务里做 HTTP 调用、文件读写等不可控耗时操作;SELECT 语句也尽量加
FOR UPDATE或LOCK IN SHARE MODE明确意图,别依赖隐式锁
慢查询背后往往是多个因素叠加,比如一个没索引的 JOIN 再遇上小 sort_buffer,就会同时触发全表扫描、磁盘排序、长版本链三重惩罚。定位时别只盯 EXPLAIN,得结合 slow_log 的 Query_time 和 Rows_examined 对比看——有时候扫了 500 万行但只花 0.2 秒,有时候扫 2 万行却卡 3 秒,后者才真正值得深挖。











