网站变慢了,数据库往往是最容易被忽略的原因。一条没走索引的 SQL,数据一多就慢到离谱,把整站拖垮。MySQL 慢查询优化是每个 DBA 和站长都必须掌握的技能:先找到慢在哪,再用 EXPLAIN 分析,最后通过加索引、改 SQL 来根治。这篇文章给你一套完整的排查和优化流程。
一、为什么 SQL 会慢
数据库慢查询常见原因:
- 全表扫描:SQL 没走索引,逐行扫整张表
- 索引失效:写了索引但查询方式没命中
- 数据量过大:表越来越大,缺乏合理的索引和分页
- 锁等待/并发:写操作互相阻塞
- 配置不当:缓冲池太小、连接数不够等
优化的思路是:先定位慢查询,再看执行计划,针对性加索引或改 SQL。
二、第一步:开启慢查询日志
MySQL 的慢查询日志会记录执行超过指定时间的 SQL。先看看状态:
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';
开启并把阈值设成 1 秒:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
也可以配置到 MySQL 配置文件 my.cnf 里永久生效:
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
用户那类敏感的写 SQL 也可以记录下来便于分析但可能泄露内容,按需关。
三、查看慢查询日志
慢查询会记录到 slow_query_log_file 指定的文件,用工具查看:
mysqldumpslow -s t /var/log/mysql/mysql-slow.log | head -20
mysqldumpslow 是 MySQL 自带的分析工具,-s t 按执行时间排序,能看到最慢的几条 SQL 和它们的执行次数。
pt-query-digest 是更强大的分析工具(Percona Toolkit),能按查询汇总统计。
四、用 EXPLAIN 分析慢 SQL
拿到一条慢 SQL 后,用 EXPLAIN 看它的执行计划:
EXPLAIN SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC;
重点看这几列:
- type:访问类型。最好的是
const/eq_ref;ref/range不错;ALL是全表扫描,最差,要重点盯 - key:实际用到的索引。为 NULL 说明没走索引
- rows:扫描的行数。越少越好,差距大说明能优化
- Extra:出现
Using filesort(文件排序)、Using temporary(临时表)通常意味着可以优化
看到 ALL + rows 很大 + key 为 NULL,基本就是"没走索引的全表扫描",最需要处理。
五、第二步:加索引(最常见手段)
1. 给经常查询/排序/关联的字段建索引:
CREATE INDEX idx_user_id ON orders(user_id);
2. 联合索引(多条件查询,注意字段顺序):
CREATE INDEX idx_user_status ON orders(user_id, status);
联合索引遵循"最左前缀"原则,(user_id, status) 能同时加速 WHERE user_id=? AND status=?,但单独查 status 用不到该索引。
3. 覆盖索引:查询的字段都在索引里,连表都不用回,性能极好。适当让 SELECT 只挑需要的字段。
六、优化 SQL 写法
除了加索引,SQL 本身也能优化:
- 避免
SELECT *:只取需要的列,减少回表和网络开销 - 避免在索引列上用函数/运算:如
WHERE DATE(created_at)='2024-01-01'会不走索引,改成范围查询created_at >= '...' AND created_at < '...' LIKE '%xx'不会走索引,要避免前置通配符- 合理分页:深层分页
LIMIT 100000,20很慢,可用"上一页最后一条 id"做条件,或用覆盖索引 - JOIN 时关联字段要有索引
七、其他常见优化
1. 检查是否有大字段/无索引的外键: 关联查询里字段要有索引。
2. 查看当前正在执行的慢 SQL/锁:
SHOW PROCESSLIST;
看到大量 Sending data、长时间 Waiting for lock,说明有慢查询或锁等待。
3. 查询缓存说明: 老版本 MySQL 有查询缓存,8.0 已移除;新版更推荐配合 Redis 做应用层缓存(参考本站 Redis 教程)。
4. 缓冲池调优(InnoDB):
[mysqld]
innodb_buffer_pool_size = 1G # 一般设为物理内存的 50%-70%
缓冲池越大,越多的数据和索引留在内存,减少磁盘 IO。
八、一个完整优化案例
问题 SQL:
SELECT * FROM orders WHERE status='pending'
AND created_at>'2024-01-01' ORDER BY created_at DESC;
EXPLAIN 显示 type=ALL、rows=500000、key=NULL——全表扫了 50 万行。
优化:
CREATE INDEX idx_status_created ON orders(status, created_at);
再 EXPLAIN,type 变 range、rows 降到几千,key 命中联合索引,Using filesort 也消失了。同样数据下,查询从几秒降到几十毫秒。
九、常见问题
Q1:加了索引还是慢?
检查是不是 type=ALL 没走索引、联合索引字段顺序不对、或查询列上用了函数导致索引失效。用 EXPLAIN 重新确认 key。
Q2:索引越多越好吗?
不是。索引写入要开销、占磁盘,太多会拖慢 INSERT 和 UPDATE。给高频查询的字段建必要索引即可,不要盲目全建。
Q3:慢查询日志一直很大?
把阈值 long_query_time 调大(如 2-3 秒),只记录真正慢的;定期清理或按天切割日志(参考本站日志切割教程)。
Q4:一条 SQL 本来慢,突然更慢?
可能是数据量增长、索引失效、或锁竞争。先看慢查询日志,再结合 EXPLAIN 和 SHOW PROCESSLIST 排查。
Q5:还是搞不定怎么办?
把慢 SQL 和 EXPLAIN 结果整理出来,配合 SHOW PROFILE/performance_schema 深入分析;必要时拆分查询或改由 Redis 缓存承担热点读。
十、结语
MySQL 慢查询优化有一套标准流程:开慢日志找慢 SQL → EXPLAIN 看执行计划 → 加索引 + 改 SQL → 验证效果。核心是让 SQL 走索引、减少扫描行数、避免 filesort。再配合缓冲池调优和 Redis 缓存,数据库就不会再成为网站的瓶颈。结合本站 MySQL 基础、Redis、PHP 性能优化教程一起看,整套优化思路就全了。
