线上系统越来越慢,用户反馈"页面转圈圈"。一看数据库,一条SQL要跑好几秒。
怎么定位慢SQL?怎么分析原因?怎么优化?今天把完整排查流程走一遍。
第一步:找到慢SQL
方法一:开启慢查询日志
-- 查看当前配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
-- 动态开启(不需要重启)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 超过1秒的记录
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
-- 查看慢查询日志内容
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 20;
方法二:SHOW PROCESSLIST实时看
SHOW FULL PROCESSLIST;
| 列 | 关注点 |
|---|---|
| Time | 执行时间,越大越可疑 |
| State | 当前状态,"Sending data"通常在计算 |
| Info | 正在执行的SQL |
第二步:分析SQL执行计划
EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 'PAID';
| 列 | 含义 | 关注点 |
|---|---|---|
| type | 访问类型 | system > const > ref > range > index > ALL |
| key | 使用的索引 | NULL说明没走索引 |
| rows | 预估扫描行数 | 越小越好 |
| Extra | 额外信息 | Using filesort、Using temporary要警惕 |
type列的关键值:
| type | 说明 | 性能 |
|---|---|---|
| system | 表只有一行 | 最快 |
| const | 主键/唯一索引等值查询 | 极快 |
| ref | 普通索引等值查询 | 快 |
| range | 索引范围扫描 | 还行 |
| index | 全索引扫描 | 慢 |
| ALL | 全表扫描 | 最慢 |
第三步:常见优化手段
优化一:加索引
-- 原始SQL:全表扫描
SELECT * FROM orders WHERE user_id = 123 AND status = 'PAID';
-- 加联合索引
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
-- 优化后:type=ref,rows大幅减少
**优化二:避免SELECT ***
-- 不好:取回所有列
SELECT * FROM orders WHERE user_id = 123;
-- 好:只取需要的列
SELECT id, order_no, amount, create_time
FROM orders
WHERE user_id = 123;
优化三:分页优化
-- 深分页很慢
SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;
-- 实际要扫描100万+20行
-- 优化:用上次最大的id做游标
SELECT * FROM orders
WHERE id > 上次最后一条的id
ORDER BY id
LIMIT 20;
-- 或者子查询优化
SELECT * FROM orders
WHERE id >= (SELECT id FROM orders ORDER BY id LIMIT 1000000, 1)
ORDER BY id
LIMIT 20;
优化四:避免索引失效
-- 不好:对索引列做运算
SELECT * FROM orders WHERE YEAR(create_time) = 2026;
-- 好:改成范围查询
SELECT * FROM orders
WHERE create_time >= '2026-01-01'
AND create_time < '2027-01-01';
-- 不好:隐式类型转换
SELECT * FROM users WHERE phone = 13800138000; -- phone是varchar
-- 好:类型匹配
SELECT * FROM users WHERE phone = '13800138000';
第四步:查看实际执行耗时
-- 开启profiling
SET profiling = 1;
-- 执行你的SQL
SELECT * FROM orders WHERE user_id = 123;
-- 查看耗时
SHOW PROFILES;
+----------+------------+----------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+----------------------------------------------+
| 1 | 0.00234567 | SELECT * FROM orders WHERE user_id = 123 |
+----------+------------+----------------------------------------------+
实战案例
案例:一个报表查询从8秒优化到0.2秒
-- 原始SQL(8秒)
SELECT u.name, COUNT(o.id) as order_count, SUM(o.amount) as total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.create_time BETWEEN '2026-01-01' AND '2026-12-31'
GROUP BY u.id, u.name
ORDER BY total DESC
LIMIT 100;
-- 问题分析:
-- 1. orders表缺少create_time索引
-- 2. GROUP BY没有覆盖索引
-- 3. LEFT JOIN其实不需要(WHERE条件已经过滤了)
-- 优化后
ALTER TABLE orders ADD INDEX idx_time_user (create_time, user_id);
SELECT u.name, t.order_count, t.total
FROM (
SELECT user_id, COUNT(*) as order_count, SUM(amount) as total
FROM orders
WHERE create_time >= '2026-01-01' AND create_time < '2027-01-01'
GROUP BY user_id
ORDER BY total DESC
LIMIT 100
) t
JOIN users u ON t.user_id = u.id;
-- 优化后0.2秒
优化思路:先在子查询中完成聚合(减少JOIN的数据量),再关联用户表取名字。
慢查询排查是后端开发的基本功。流程就是:找到慢SQL → EXPLAIN分析 → 针对性优化 → 验证效果。
建议把自己项目的慢查询日志打开看看,可能有很多惊喜。