怎么看慢查询日志里真正耗时的 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 才能走索引。
