慢查询指的是数据库中执行时间超过指定阈值的 SQL 语句。不同业务场景下,这个阈值通常各不相同。在我们公司内部,这个阈值被设定为 1 秒钟。也就是说,任何执行时间超过 1 秒的 SQL 语句都会被视为慢查询。
对慢查询进行问题排查通常分为以下几个步骤:
发现问题
一般而言,慢查询问题相对容易发现。如果有完善的监控体系,系统会定期统计慢 SQL 并通过报警方式提醒。
此外,如果使用了某些数据库中间件,例如 TDDL,它们通常会记录慢 SQL 的日志:
Cause: ERR-CODE: [TDDL-4202][ERR_SQL_QUERY_TIMEOUT] Slow query leads to a timeout exception, please contact DBA to check slow sql. SocketTimout:12000 ms,
如果只依赖 MySQL 本身的话
- 找到 MySQL 的配置文件 my.cnf(或者在 Windows 系统下可能是 my.ini),通常它们位于 MySQL 安装目录下的 etc 或 conf 文件夹中。
- 启用慢查询日志功能:请找到以下配置项,并将其取消注释(如果已注释),确保以下行存在或添加到配置文件中:
slow_query_log = 1
slow_query_log_file = /path/to/slow-query.log
long_query_time = 1
- 保存配置文件后,重新启动 MySQL 服务以使配置生效。
- 查看慢查询日志:使用文本编辑器打开慢查询日志文件。日志文件的路径通常在配置文件中指定。例如,在 Linux 系统上,可以使用以下命令来查看慢查询日志文件:
sudo vi /var/log/mysql/mysql-slow.log
请将路径 /var/log/mysql/mysql-slow.log 替换为实际配置文件中指定的慢查询日志路径。配置完毕后,MySQL 会将执行时间超过 long_query_time 设置的时间阈值的 SQL 语句记录到慢查询日志中。
如果有慢 SQL,内容如下:
# Time: 2023-06-04T12:00:00.123456Z
# User@Host: hollis[192.168.0.1]:3306
# Query_time: 2.345678 Lock_time: 0.012345 Rows_sent: 10 Rows_examined: 100
SET timestamp=1650000000;
SELECT * FROM orders WHERE status = 'pending' ORDER BY gmt_created DESC;
定位问题
在上述各种监控、报警和日志中,我们可以定位到具体的慢 SQL 语句,然后可以进一步分析为什么这个 SQL 语句执行缓慢,主要是排查以下几个可能的原因:
- 缺少索引:没有为查询涉及的列创建适当的索引,导致数据库需要全表扫描来找到匹配的行。
- 错误使用索引:使用了索引但不符合最左前缀原则,或者索引选择度不高(即索引列的唯一性不够高),导致数据库选择不到最优的索引执行查询。
- 查询字段过多:SELECT 语句中涉及的字段过多,增加了数据传输和处理的开销。
- 多次回表:查询执行过程中需要多次访问磁盘以获取额外的数据行,例如对主键的索引扫描后,需要再次根据主键进行查询。
- 多表连接:涉及多个表的 JOIN 操作,若 JOIN 条件不合适或者 JOIN 操作没有利用到索引,会导致性能下降。
- 深度分页:需要返回大量数据中的某一页,但是没有合适的方式来快速定位和获取这一页数据。
- 其他因素:还有一些其他可能的原因,例如复杂的子查询、数据库服务器负载高、SQL 语句写法不佳等。
针对这些问题,可以通过优化数据库表结构、添加合适的索引、优化 SQL 语句写法、调整数据库配置参数等方式来改进 SQL 查询的性能。
对于大多数情况下的慢 SQL 问题,通常可以通过执行计划分析找出根本原因,主要集中在索引和 JOIN 操作上。
解决问题
定位问题后,解决问题就会变得容易起来。
实际上,最大的挑战不在于解决问题,而在于准确定位问题。因为一旦问题被准确定位,解决起来就变得相对简单。例如,缺少索引就添加索引,JOIN 操作过多就进行拆分。这里不再详细展开。