前言
MySQL 查询慢是后端开发最常遇到的问题。很多人遇到慢SQL第一反应就是加索引,但索引不是万能解药,不合理的索引、糟糕的SQL写法、不当的表结构,依旧会导致全表扫描。
本文从原理到实战,梳理MySQL查询优化整套思路,覆盖索引、SQL编写、表设计、配置、业务架构层面,附带常见踩坑案例,适合开发自查慢查询。
环境:MySQL5.7 / 8.0,InnoDB引擎。
一、先找到慢SQL:优化的前提
优化不是凭感觉,第一步是定位慢查询。
1. 开启慢查询日志
sql
-- 查看慢查询配置
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';
slow_query_log=ON开启慢日志long_query_time:阈值,单位秒,超过该时间的SQL会被记录,开发环境建议设置0.5slog_queries_not_using_indexes=ON:记录没有使用索引的SQL
2. explain 分析执行计划
EXPLAIN 是最核心工具,放在SELECT前面,看SQL如何执行。
sql
EXPLAIN SELECT * FROM user WHERE phone = '13800138000';
重点看这几个字段:
type:访问类型,性能从优到劣:system > const > eq_ref > ref > range > index > ALL,ALL代表全表扫描,需要优化key:实际使用的索引,NULL代表没有用到索引rows:预估扫描行数,数值越大越危险Extra:额外信息,Using filesort(文件排序)、Using temporary(临时表)都是性能杀手
优化目标:尽量避免
type=ALL,消除Using filesort、Using temporary。
二、索引优化:提升查询最核心手段
InnoDB是B+树索引,索引的本质是减少扫描行数。
1. 最左前缀原则(联合索引重中之重)
联合索引 idx_a_b_c(a,b,c),生效顺序:从左往右匹配,遇到范围查询(> < like %xx)停止。
✅ 可以命中索引:
where a=?、where a=? and b=?、where a=? and b=? and c=?
❌ 无法命中:
where b=?、where c=?,跳过最左列,索引失效。
建索引顺序原则:等值条件放前面,范围条件放最后。
2. 索引失效常见场景(高频踩坑)
- 索引列参与运算、函数操作
sql
-- 失效!索引列做函数
SELECT * FROM user WHERE DATE(create_time) = '2026-10-01';
-- 改写,把函数移到常量一侧
SELECT * FROM user WHERE create_time >= '2026-10-01' AND create_time < '2026-10-02';
- 隐式类型转换:字符串字段用数字查询
sql
-- phone是varchar,传入数字,触发隐式转换,索引失效
SELECT * FROM user WHERE phone = 13800138000;
LIKE '%关键词',前缀模糊匹配,索引失效;LIKE '关键词%'可以走索引- OR 两边字段有一方无索引,整个索引失效
- 优化器判断全表扫描更快,主动放弃索引(数据量占比过高)
3. 不要盲目建索引
索引优点:加快查询;缺点:占用磁盘,降低写入性能(INSERT/UPDATE/DELETE要维护索引)。
- 一张表索引不宜过多,建议不超过6个;
- 区分度低的字段不要建索引(性别、状态只有0/1,索引几乎无效);
- 优先使用联合索引,减少单字段冗余索引。
4. 覆盖索引(避免回表)
InnoDB二级索引叶子节点存主键,查到索引后需要回主键索引读取完整行,叫回表。
覆盖索引:索引包含查询需要的所有字段,不需要回表,性能极高。
sql
-- 需求:只查id,username,phone
-- 建立联合索引 idx_username_phone (username, phone)
SELECT id,username,phone FROM user WHERE username='zhangsan';
Explain Extra列会出现 Using index,代表命中覆盖索引。
三、SQL写法优化:改掉坏习惯
1. 禁止 SELECT *
只查询业务真正需要的字段。
- 减少网络传输;
- 有机会触发覆盖索引,避免回表;
- 减少内存占用。
2. 分页大偏移优化
OFFSET 100000 LIMIT 10,会先扫描10万行再丢弃,性能极差。
❌ 低效写法
sql
SELECT * FROM order LIMIT 100000,10;
✅ 主键书签优化(推荐)
sql
SELECT * FROM order WHERE id > 100000 LIMIT 10;
如果主键不连续,也可以用子查询先定位主键:
sql
SELECT t.* FROM order t
INNER JOIN (SELECT id FROM order ORDER BY id LIMIT 100000,10) AS tmp ON t.id = tmp.id;
3. 优化JOIN关联查询
- 小表驱动大表,
INNER JOIN优化器会自动选择,但尽量保证关联字段建立索引; - JOIN字段类型、字符集必须一致,防止隐式转换;
- 避免多表JOIN,超过3张表关联建议业务层拆分;
- 不要在子查询里写大表,
EXISTS适合大表,IN适合小集合。
原则:
IN集合数量不要过大,建议控制在1000以内。
4. 排序与分组优化
ORDER BY、GROUP BY 尽量利用索引有序特性,避免 Using filesort。
- 排序字段放到联合索引末尾;
- 尽量减少排序字段;
- 大数据量分组优先在业务层处理。
四、表结构设计优化(从源头减少慢查询)
1. 字段类型选择,越小越好
- 整型优先用
TINYINT / INT,不要全部用BIGINT; - 字符串优先
VARCHAR,不要用TEXT; - 非必要不使用大字段TEXT/BLOB,大字段会单独存储,读取开销大;
- 时间优先DATETIME,不要用VARCHAR存储时间。
2. 主键设计
InnoDB主键是聚簇索引:
- 主键推荐自增INT/BIGINT,无序主键(UUID)会造成页分裂,写入变慢;
- 主键尽量短,二级索引会携带主键,主键越长,二级索引体积越大。
3. 分表分区
单表数据量千万级以上,索引体积变大,查询性能下降。
- 分区表:适合单表冷热数据分离,按时间分区(日志、订单);
- 水平分表:按用户ID、订单ID哈希拆分,适合超大数据量;
注意:分区、分表会增加业务复杂度,不要过早优化。
五、MySQL配置层面调优
核心参数(根据服务器内存调整)
innodb_buffer_pool_size:最重要参数,InnoDB缓存池,推荐服务器物理内存的50%~70%,缓存索引和数据页,减少磁盘IO;innodb_log_file_size:redo log大小,提升写入性能;sort_buffer_size、join_buffer_size:会话级缓存,不要设置过大,容易内存溢出;max_connections:连接数,按需调整,不要盲目开大。
调优原则:优先调
innodb_buffer_pool_size,大部分性能瓶颈是磁盘IO。
六、业务架构层面优化(终极方案)
当SQL、索引、参数优化到极限,还慢,就要考虑架构方案:
- 读写分离:主库写,从库承担查询压力;
- 缓存:热点数据放Redis,拦截高频查询,直接绕开MySQL;
- 预计算:报表、统计类查询不要实时查MySQL,定时预计算结果存入汇总表;
- 搜索引擎:全文检索、多条件复杂检索,迁移到Elasticsearch,减轻MySQL压力。
七、常见误区
- 索引越多查询越快:错,索引会拖慢写入;
- EXPLAIN看到索引就一定很快:要看扫描rows,索引区分度差依旧慢;
- 调优只改参数,不改SQL:绝大多数慢查询根源是SQL和索引;
- 用缓存就不用优化SQL:缓存失效瞬间,大量请求打满数据库,雪崩风险。
八、优化通用步骤总结
- 开启慢查询日志,捕获慢SQL;
- EXPLAIN分析执行计划,定位全表扫描、文件排序;
- 优化SQL写法,调整索引,优先利用覆盖索引;
- 优化表结构,精简字段;
- 调整MySQL核心配置;
- 业务层引入缓存、读写分离、分表等架构方案。
小结
MySQL查询优化是分层的:优先优化SQL和索引,其次优化表结构,再调整数据库参数,最后才考虑架构改造 。
很多时候一个不合理的WHERE条件、失效的索引,就足以让数据库性能下降几十倍。优化前一定要用explain验证,不要凭经验猜测。