一、准备阶段:发现问题与监控

在优化之前,首先要定位到有问题的SQL。

  1. 开启慢查询日志: 这是最直接有效的方法。通过配置MySQL的long_query_time参数,记录下所有执行时间超过阈值的SQL语句,然后针对这些慢SQL进行重点分析。
  2. 利用监控工具: 使用如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 BYORDER BY子句。这也会影响性能。
    • Using index: 这是个好信号,表示使用了“覆盖索引”,查询的列全部在索引中,无需回表。

2. 索引优化(最有效的手段之一)

  • 避免索引失效: 确保创建的索引被真正用上。常见的索引失效场景包括:
    • 对索引列进行函数计算、表达式计算(如WHERE YEAR(create_time) = 2023)。
    • 索引列发生了隐式类型转换(如字符串字段传了数字)。
    • 使用!=<>OR(并非绝对,优化器可能会选择合并索引)。
    • 使用LIKE以通配符%开头(如LIKE '%abc')。
    • 复合索引未遵守最左前缀原则。
  • 选择合适的索引列:WHEREGROUP BYORDER BYJOIN ... 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来连接条件: 可以考虑使用UNIONUNION ALL来替代,或者改写为IN()
  • 使用JOIN代替子查询: 在大多数情况下,JOIN的性能优于子查询。因为MySQL对子查询的优化不如JOIN。
  • 注意IN和EXISTS的使用:
    • 如果子查询结果集小,而外表大,用EXISTS
    • 如果子查询结果集大,而外表小,用IN
  • 批量操作: 在需要插入或更新大量数据时,使用batch批处理,而不是循环单条操作。
三、架构与设计层面优化

当单条SQL和索引优化到极致后,如果性能仍然达不到要求,就需要从更高层面考虑了。

  1. 数据库设计优化
    • 合理的表结构: 遵循三范式,但有时为了性能可以适当进行反范式化设计,比如增加冗余字段以减少JOIN。
    • 选择合适的数据类型: 使用更小的、简单的数据类型(如INT vs VARCHAR),使用NOT NULL约束。
  2. 读写分离: 主数据库负责写操作,多个从数据库负责读操作,分摊读压力。这在读多写少的场景下非常有效。
  3. 分库分表: 当单表数据量过大(如千万级以上)或数据库实例压力过大时,可以考虑水平分表或垂直分表,以及分库。
    • 水平分表: 按某个字段(如用户ID)的规则,将数据分散到多个结构相同的表中。
    • 垂直分表: 将一张宽表按冷热字段拆分成多个表。

总结

“总而言之,SQL调优是一个持续的过程。我的思路通常是:先监控发现慢SQL,然后通过EXPLAIN分析执行计划,接着从索引和SQL语句本身这两个成本最低的方面入手进行优化。如果这些手段都达到了瓶颈,最后才会考虑读写分离、分库分表等架构层面的方案。


面试加分项

  • 提到具体版本特性: 比如“在MySQL 8.0中,引入了窗口函数,可以更高效地解决一些复杂查询,替代低效的自连接。”
  • 结合Java应用层: 比如“在我们的Java应用中,会使用Druid连接池并开启SQL防火墙功能,来监控和阻断性能很差的SQL。”
  • 讲述一个实际案例: 如果你有真实的调优经历,可以简单讲一个故事:“我曾经遇到一个分页查询非常慢,通过EXPLAIN发现它 filesort 了,然后我通过使用覆盖索引+游标分页的方式,将查询时间从2秒优化到了50毫秒。”
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐