应重点查看慢查询日志中的Rows_examined和Rows_sent字段,若前者远大于后者(如124892 vs 10),说明存在索引缺失或失效;同时开启log_queries_not_using_indexes以捕获潜在风险语句。

怎么看慢查询日志里真正耗时的 sql?
mysql 的慢查询日志(slow_query_log)默认只记录执行时间超过 long_query_time 的语句,但光看耗时容易误判——比如一条 select 花了 2.3 秒,可能是因为没走索引全表扫描,也可能只是返回了 50 万行结果导致网络或客户端卡顿。
关键要看日志里的两个字段:Rows_examined(扫描行数)和 Rows_sent(返回行数)。如果前者远大于后者(比如 Rows_examined: 124892,Rows_sent: 10),基本可以断定是索引缺失或失效。
- 确保日志开启并记录足够信息:
SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; SET GLOBAL log_queries_not_using_indexes = ON; -
log_queries_not_using_indexes很重要——它会把没走索引的查询也记进来,哪怕执行很快,这类语句在数据增长后极易变慢 - 用
mysqldumpslow快速聚合分析,例如:mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log(按总耗时排序,取前 10 条)
EXPLaiN 看懂执行计划的关键指标
EXPLAIN 不是“看看就行”,要盯住几个硬指标:
-
type字段:优先级从高到低是const≈eq_ref>ref>range>index>ALL。出现ALL就是全表扫描,必须优化 -
key字段:显示实际使用的索引名。如果是NULL,说明没走索引(注意:也可能是用了索引但被优化器放弃,需结合possible_keys和Extra判断) -
Extra字段:警惕Using filesort(需要额外排序)和Using temporary(临时表),尤其是二者同时出现,往往意味着ORDER BY+GROUP BY没命中索引覆盖
示例:对 orders 表查最近 7 天未支付订单
EXPLAIN SELECT * FROM orders WHERE status = 'pending' AND created_at > NOW() - INTERVAL 7 DAY;
若 type 是 ALL,且 key 是 NULL,说明 status 和 created_at 缺少联合索引;建索引应按「等值条件在前、范围条件在后」原则:INDEX(status, created_at)。
联合索引顺序怎么排才不踩坑?
联合索引不是字段随便堆砌,顺序直接影响能否命中。核心规则就一条:等值查询字段放前面,范围查询字段放后面。
- 错误示范:
INDEX(created_at, status)——WHERE status = 'pending' AND created_at > '2024-01-01'只能用上status的等值部分,created_at的范围无法利用索引排序,type仍可能是range或更差 - 正确写法:
INDEX(status, created_at)—— 等值status定位数据块,再在块内按created_at范围扫描,type可达range,且支持后续ORDER BY created_at - 如果还有
ORDER BY user_id,且user_id是等值条件,可加到索引末尾:INDEX(status, created_at, user_id);但若user_id是范围或IN列表,则不能继续延伸索引
哪些情况加了索引也没用?
不是所有字段都适合建索引,有些场景建了反而拖慢写入、浪费空间,甚至让优化器选错执行路径:
- 低区分度字段:比如
gender(只有 ‘M’/’F’),is_deleted(99% 是 0),索引选择性太差,优化器大概率直接全表扫描 - 频繁更新的字段:每条
UPDATE都要维护索引树,写入压力大时得权衡读写比 -
LIKE左模糊:WHERE name LIKE '%abc'无法使用索引;右模糊'abc%'可以,但中间模糊'%abc%'不行(除非用全文索引或倒排) - 隐式类型转换:
WHERE phone = 13800138000(phone是VARCHAR),MySQL 会把字段转成数字比较,导致索引失效;应统一为字符串:WHERE phone = '13800138000'
最常被忽略的一点:索引列参与计算或函数,比如 WHERE YEAR(created_at) = 2024,哪怕 created_at 有索引也完全用不上。改写为范围查询:WHERE created_at >= '2024-01-01' AND created_at 才能走索引。