在面试中,SQL 调优经常是被问及的问题,它可以考察候选人对于 SQL 整体性能优化的理解和掌握程度。一般来说,SQL 调优的步骤可以从以下几个方面入手。
首先,需要准确地定位问题。在面试中,最好能结合具体的业务场景进行说明,例如某次线下报警引发的慢 SQL 问题,或者性能分析显示接口响应时间过长,根源是 SQL 查询效率不佳。无论何种情况,都需要提供背景信息。
一旦问题定位清楚,接下来就是对问题进行深入分析。
首先,需要通过各类监控平台或工具准确定位到具体的 SQL 语句。一旦定位到了问题 SQL 语句,我们就能够确定是哪张表或哪个 SQL 语句执行速度较慢。
接下来,需要进行详细的分析。一般而言,一个 SQL 语句执行缓慢可能有以下几种原因:
- 索引未被有效利用
- 多表连接
- 查询字段过多
- 数据量过大的表
- 索引的区分度不高
- 数据库连接数不足
- 数据库表结构不合理
- 数据库的 IO 或 CPU 负载过高
- 数据库参数设置不合理
- 长时间事务
- 锁竞争引起的等待
因此,进行一次全面的 SQL 调优时,通常需要考虑上述几个因素,往往会涉及其中一个或多个问题。接下来,需要逐一进行优化。
首先,处理索引失效的问题通常要通过执行计划分析是否正确使用了索引,以及使用的索引是否符合预期。如果索引设计不合理或者因索引失效导致问题,可以考虑调整索引设计,修改 SQL 语句,或者强制使用特定的索引。索引失效可参考历史文章:
其次,多表连接(join)也是导致 SQL 执行速度较慢的常见原因之一。
接下来,如果是索引区分度不高的话,这个其实也和索引不合理有关,但是其实到底快不快,用不用索引,并不是因为区分度高不高导致,其实还是索引扫描的行数的成本导致。所以,有的时候不能认为区分度不高就一定会效率低,或者一定就不适合创建索引。
查询字段过多有时是因为误用了 SELECT *,通常情况下,查询少于 100 个字段并不是大问题,除非字段数目极多。解决方法有两种:一是只查询必要的字段,避免检索不需要的数据;二是进行垂直分表,将数据分散存储到多张表中。然而,这种分散存储也可能带来需要多表连接的问题,因此在进行分表时需要考虑数据冗余的问题。对于表中数据量过大的情况,一般而言,超过 1000 万条数据会显著降低查询效率,即使使用了索引也可能不够快。因此,解决方法包括:
- 数据归档,将历史数据移出,只保留近期数据,例如保留最近半年数据,将半年前的数据归档。
- 分库分表或分区。通过拆分数据来分散存储,以减轻单表的压力。具体的分库分表和分区策略可以参考详细文档,这里不展开说明。
- 考虑使用支持大数据量查询的第三方数据库,如 OceanBase、TiDB,或者搜索引擎如 Elasticsearch 等。
数据库连接数不足也需要具体分析原因。可能原因包括:业务量过大,单个数据库无法处理;存在慢 SQL 或长事务导致连接阻塞,进而影响其他查询速度。
数据库表结构不合理通常是一个关键原因。例如,某些字段可能存储了过长的内容,或者没有进行合理的数据冗余,导致需要频繁进行多表关联查询等情况。解决方法通常是进行数据库结构重构或者进行表的分解。
数据库的 IO 或 CPU 负载较高也是常见问题。当数据库整体的 IO 或 CPU 负载升高时,查询速度可能会受到影响。因此,需要深入分析其背后的原因,并采取相应的解决策略。
存在长事务和慢 SQL 类似,都会占用数据库连接,从而导致其他请求需要等待。
锁竞争导致的等待则是在高并发情况下,多个请求竞争共享资源,导致锁定等待时间增长,进而使得 SQL 执行变慢。这一过程也可以参考上述导致 CPU 负载过高的问题。
数据库参数设置不合理也是常见问题,针对具体的业务场景进行适当的参数调整,有时能显著提升 SQL 的效率。例如调整内存大小、缓存大小以及线程池大小等。
扩展知识
参数优化
假设我们管理的数据库名为 mydb,其中包含一个名为 mytable 的 InnoDB 表。该表具有自增主键 id,一个整数类型的 age 字段和一个字符串类型的 name 字段。我们希望对这个表进行优化。
首先,可以通过执行 SHOW VARIABLES LIKE 'innodb%'; 命令来查看当前 InnoDB 参数的设置情况。这些参数涵盖了缓冲池大小、刷新间隔、日志大小等核心设置。
接下来,我们可以尝试调整几个关键参数来优化数据库的性能:
innodb_buffer_pool_size:缓冲池大小是 InnoDB 存储引擎的关键参数之一,它决定了 InnoDB 存储引擎在内存中使用的大小。通常建议将该参数设置为系统可用内存的 70% 到 80%。例如,如果系统总内存为 8GB,我们可以将 innodb_buffer_pool_size 设置为 6GB。在 MySQL 中,可以使用以下命令进行设置:
SET GLOBAL innodb_buffer_pool_size=6G;
**innodb_read_io_threads 和 innodb_write_io_threads **这两个参数控制着 InnoDB 存储引擎的 I/O 线程数量。一般建议将它们设置为可用 CPU 核心数的一半。在 MySQL 中,您可以使用以下命令进行设置:
SET GLOBAL innodb_read_io_threads=4;
SET GLOBAL innodb_write_io_threads=4;
innodb_log_file_size 参数控制着事务日志文件的大小。默认情况下,其大小为 5M,这通常是不足够的。在 MySQL 中,您可以使用以下命令进行设置:
SET GLOBAL innodb_log_file_size=1G;
一般来说,在设置这个参数之前,需要先进行数据采样。可以观察业务高峰期约 2 小时内写入的日志量,然后将这个量作为设定事务日志文件大小的参考。通常建议设置为约 1G 左右,或者系统内存的 1/4。
区分度不高的字段建索引一定没用吗
关于刚刚上面提到的区分度不高的字段。做一下解释,这个区分度不高的字段建立索引到底有没有用呢。
答案是:不一定。
在某些情况下,索引的有效性并不完全取决于字段的区分度。例如,如果一个表中包含性别字段,仅有两个可能的取值:男和女,那么通常情况下这个字段的区分度较低,使用该字段进行查询可能无法有效地过滤大量数据,从而无法充分发挥索引的优势。
然而,也存在特殊情况。比如,如果性别的分布比例是 95%男性和 5%女性,那么当以"女"作为性别查询条件时,依然可以通过索引进行高效查询,因为它能够快速过滤掉大部分数据,从而提升性能。这种情况下,索引仍然能够显著提升效率。
类似的情况在任务表中也很常见。例如,任务表中可能有一个状态字段,大多数任务处于成功状态(SUCCESS),只有少数任务处于初始化状态(INIT)。在这种情况下,为状态字段添加索引可以显著提升查询效率。这样在扫描任务表并执行任务时,可以更快地定位到需要处理的任务。
因此,虽然字段的区分度影响索引的效果,但在特定的数据分布情况下,即使区分度不高的字段仍然可以通过索引来优化查询性能。