SQL调优
本文介绍了SQL性能优化的系统化方法,主要分为三个阶段:准备阶段通过慢查询日志和监控工具发现问题SQL;核心阶段使用EXPLAIN分析执行计划并针对性优化索引和SQL语句;架构层面则考虑分库分表等方案。优化优先级是先解决单条SQL和索引问题,再考虑架构调整。文章还提供了索引失效场景、分页优化等实用技巧,以及面试时可展示的加分项,强调SQL调优是一个持续改进的过程。
·
一、准备阶段:发现问题与监控
在优化之前,首先要定位到有问题的SQL。
- 开启慢查询日志: 这是最直接有效的方法。通过配置MySQL的
long_query_time参数,记录下所有执行时间超过阈值的SQL语句,然后针对这些慢SQL进行重点分析。 - 利用监控工具: 使用如Arthas、SkyWalking、Prometheus等APM工具,或者云服务商提供的数据库监控,来观察数据库的QPS、TPS、连接数、慢SQL趋势等指标。
二、核心阶段:分析与优化
定位到慢SQL后,就到了最核心的分析和优化环节。
1. 使用EXPLAIN进行分析
这是SQL调优的“王牌工具”。拿到一条SQL,第一件事就是使用EXPLAIN或者EXPLAIN FORMAT=JSON来查看其执行计划,重点关注以下几个字段:
- type: 访问类型,从好到坏大致是:
system > const > eq_ref > ref > range > index > ALL。至少要优化到range级别,最好是ref。- 举例: 如果看到
type=ALL,就代表全表扫描,这时就需要考虑加索引了。
- 举例: 如果看到
- key: 实际使用的索引。如果为NULL,则说明没有使用索引。
- rows: 预估需要扫描的行数。这个值越小越好。
- Extra: 包含非常关键的信息。
Using filesort: 表示MySQL无法利用索引完成排序,需要额外的排序操作。这是需要优化的信号。Using temporary: 表示使用了临时表,常见于GROUP BY和ORDER BY子句。这也会影响性能。Using index: 这是个好信号,表示使用了“覆盖索引”,查询的列全部在索引中,无需回表。
2. 索引优化(最有效的手段之一)
- 避免索引失效: 确保创建的索引被真正用上。常见的索引失效场景包括:
- 对索引列进行函数计算、表达式计算(如
WHERE YEAR(create_time) = 2023)。 - 索引列发生了隐式类型转换(如字符串字段传了数字)。
- 使用
!=、<>、OR(并非绝对,优化器可能会选择合并索引)。 - 使用
LIKE以通配符%开头(如LIKE '%abc')。 - 复合索引未遵守最左前缀原则。
- 对索引列进行函数计算、表达式计算(如
- 选择合适的索引列: 在
WHERE、GROUP BY、ORDER BY、JOIN ... ON条件中出现的列,考虑建立索引。 - 使用复合索引: 将多个经常一起查询的列做成一个复合索引,并注意最左前缀原则。这可以减少单列索引的数量。
- 覆盖索引: 如果一个索引包含了查询所需要的所有字段,那么数据库就不需要回表查询,可以极大地提升性能。
- 举例:
SELECT id, name FROM user WHERE name = 'xxx';,如果我们在(name)上建立了索引,那么它就是覆盖索引。
- 举例:
- 索引下推: 这是MySQL 5.6引入的特性,可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
3. SQL语句优化
- 避免使用
SELECT *: 只取需要的字段。这可以减少网络传输开销,更重要的是增加了使用覆盖索引的可能性。 - 优化分页查询: 对于
LIMIT M, N这种深度分页,偏移量M很大时性能很差。- 优化方案: 使用
WHERE id > ? LIMIT N的方式,通过上一页的最大ID来定位,俗称“游标分页”。
- 优化方案: 使用
- 避免使用
OR来连接条件: 可以考虑使用UNION或UNION ALL来替代,或者改写为IN()。 - 使用JOIN代替子查询: 在大多数情况下,JOIN的性能优于子查询。因为MySQL对子查询的优化不如JOIN。
- 注意IN和EXISTS的使用:
- 如果子查询结果集小,而外表大,用
EXISTS。 - 如果子查询结果集大,而外表小,用
IN。
- 如果子查询结果集小,而外表大,用
- 批量操作: 在需要插入或更新大量数据时,使用
batch批处理,而不是循环单条操作。
三、架构与设计层面优化
当单条SQL和索引优化到极致后,如果性能仍然达不到要求,就需要从更高层面考虑了。
- 数据库设计优化
- 合理的表结构: 遵循三范式,但有时为了性能可以适当进行反范式化设计,比如增加冗余字段以减少JOIN。
- 选择合适的数据类型: 使用更小的、简单的数据类型(如
INTvsVARCHAR),使用NOT NULL约束。
- 读写分离: 主数据库负责写操作,多个从数据库负责读操作,分摊读压力。这在读多写少的场景下非常有效。
- 分库分表: 当单表数据量过大(如千万级以上)或数据库实例压力过大时,可以考虑水平分表或垂直分表,以及分库。
- 水平分表: 按某个字段(如用户ID)的规则,将数据分散到多个结构相同的表中。
- 垂直分表: 将一张宽表按冷热字段拆分成多个表。
总结
“总而言之,SQL调优是一个持续的过程。我的思路通常是:先监控发现慢SQL,然后通过EXPLAIN分析执行计划,接着从索引和SQL语句本身这两个成本最低的方面入手进行优化。如果这些手段都达到了瓶颈,最后才会考虑读写分离、分库分表等架构层面的方案。”
面试加分项
- 提到具体版本特性: 比如“在MySQL 8.0中,引入了窗口函数,可以更高效地解决一些复杂查询,替代低效的自连接。”
- 结合Java应用层: 比如“在我们的Java应用中,会使用Druid连接池并开启SQL防火墙功能,来监控和阻断性能很差的SQL。”
- 讲述一个实际案例: 如果你有真实的调优经历,可以简单讲一个故事:“我曾经遇到一个分页查询非常慢,通过EXPLAIN发现它 filesort 了,然后我通过使用覆盖索引+游标分页的方式,将查询时间从2秒优化到了50毫秒。”
更多推荐


所有评论(0)