SQL 调优的完整链路是:
定位慢 SQL → 看执行计划 → 分析扫描/回表/排序/连接/锁 → 改 SQL 或索引 → 更新统计信息 → 必要时改表结构/参数/架构 → 生产验证。
发现慢 SQL
↓
定位问题
↓
EXPLAIN 分析执行计划
↓
判断:索引?SQL写法?数据量?表结构?
↓
优化
↓
重新 EXPLAIN / 压测
↓
确认执行效率提升
定位慢 SQL
-
开启慢查询日志 slow_query_log,设置阈值(比如超过 1s 的 SQL 记录下来)
-
MySQL:show processlist 查看正在执行的长耗时 SQL
-
核心工具:explain 分析执行计划
EXPLAIN------SQL 调优最重要的工具
EXPLAIN SELECT id, user_id, amount
FROM orders
WHERE user_id = 123 AND status = 1
ORDER BY created_at DESC
LIMIT 10;
|---------------|-------------------------------------------------------------------------|
| 字段 | 含义 |
| type | 访问类型,性能从好到差大致:system > const > eq_ref > ref > range > index > ALL |
| possible_keys | 可能用的索引 |
| key | 实际用的索引 |
| key_len | 索引使用长度,可判断联合索引用了几列 |
| rows | 预估扫描行数 |
| filtered | 过滤后剩余比例 |
| Extra | 额外信息,最关键( Extra 经常能够直接暴露 SQL 的问题。) |
常见 Extra:
-
Using index:覆盖索引,无需回表,好。
-
Using where:存储引擎返回后还要过滤。
-
Using index condition:索引条件下推。
-
Using temporary:用了临时表,常见于 GROUP BY、DISTINCT、UNION。
-
Using filesort:额外排序,常见于 ORDER BY 无法用索引。
-
Using join buffer:连接无索引,可能较慢。
type=ALL 通常代表全表扫描;type=index 是扫描整个索引,也不一定好。
索引优化
1. 索引底层
InnoDB 是 B + 树索引,主键是聚簇索引;普通索引(二级索引)叶子节点存主键值,回表查询拿完整数据。
2. 索引失效常见场景
-
索引列做函数运算、隐式类型转换,where substr(name,1,2)='xx'、where id='123'(id 是 int)
-
索引使用不等于!=、<>、not in、is not null,会放弃索引
-
like '%关键词' 前缀模糊查询,不走索引;like '关键词%' 可以走
-
联合索引最左前缀原则,跳过左边字段,后面索引失效
-
MySQL 优化器判断全表扫描比索引更快,主动放弃索引(数据量少时常见)
3. 联合索引设计原则
- 把等值查询放前面,范围查询(> < between)放最后
例:where a=1 and b>2 and c=3,联合索引 (a,b),c 无法用到索引
-
区分度高的字段优先放在联合索引靠前位置
-
尽量建立覆盖索引:查询的所有字段都在索引里,避免回表(Extra:Using index)
SQL 语句写法优化
-
只取需要列,不要 select *,只查业务需要的字段,减少回表、网络传输
-
减少子查询,优先改成 JOIN;子查询容易创建临时表
-
IN 里面数据量大的时候,替换成 JOIN;小集合可以用 IN
-
避免在大事务里执行大查询;大查询拆成分页查询
-
深分页优化:limit 10000,20 ( 扫描/定位前1000000条, 再取20条)偏移量大性能差, 使用基于游标/上一次最大 ID 的分页:where id>10000 limit 20
-
OR 尽量拆分,或者建立包含 OR 所有字段的联合索引
-
批量操作:用 insert values (...),(...) 代替多次单条 insert,减少事务提交次数
表结构优化
-
字段类型尽量小:能用 int 不用 bigint,varchar 设置合理长度,char 固定短字符
-
禁止 NULL:NULL 会占用额外空间,索引效率差,用默认值代替
-
大字段(text、blob)单独拆到扩展表,减少主表行数据大小
-
适当分表:水平分表(按时间 / 设备 id,IoT 项目常用)、垂直分表。分库分表是最后手段,会带来分布式事务、跨片查询复杂度。
-
主键设计:自增主键,避免随机主键(UUID 会造成页分裂,B + 树频繁调整)
架构层面优化
-
读写分离:主库写,从库读,查询流量走从库
-
缓存:热点查询放入 Redis,挡掉大部分 DB 查询,减少 SQL 请求
-
时序数据(IoT 设备上报):使用 TDengine 这类时序库,不要全部塞进 MySQL
-
分库分表:海量数据场景,Sharding-JDBC 做分库分表,注意跨库 join、分页、排序问题
-
消息队列:削峰填谷。
-
搜索引擎:全文检索走 ES。
-
OLTP/OLAP 分离:报表走数仓/列存。
数据库配置优化
my.cnf/my.ini
-
innodb_buffer_pool_size:InnoDB 缓冲池,核心,一般设置服务器内存 50%~70%,缓存索引和数据页,减少磁盘 IO
-
innodb_flush_log_at_trx_commit 刷盘策略,平衡性能和数据安全
-
调整连接数、排序缓冲区 sort_buffer_size、join 缓冲区 join_buffer_size,不建议盲目调大