查询没走索引,是最常见的慢原因
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 秒,后者才真正值得深挖。
