SQL慢查询日志是数据库自带的审计机制,用来记录执行时间超过指定阈值或者未使用索引的SQL语句。通过它,开发者和DBA能够把性能瓶颈从模糊的“数据库慢”缩小到某几条具体的查询上。不同数据库的实现略有差异,本文以最常见的MySQL为例说明完整的开启与分析流程。

一、慢查询日志的开启方式
MySQL的慢查询日志受多个系统变量控制,最核心的是slow_query_log、slow_query_time以及log_output。默认情况下slow_query_log的值为OFF,也就是说日志功能处于关闭状态。我们可以通过命令行动态修改,也可以写进配置文件实现永久生效。
动态开启适合临时排查,不需要重启数据库实例。在MySQL客户端中执行下列语句即可:
-- 开启慢查询日志 SET GLOBAL slow_query_log = 'ON'; -- 设置慢查询阈值为0.5秒 SET GLOBAL long_query_time = 0.5; -- 将日志输出到文件 SET GLOBAL log_output = 'FILE'; -- 指定日志文件路径 SET GLOBAL slow_query_log_file = '/var/lib/mysql/slow.log';
上述参数中,long_query_time的单位是秒,支持小数点后精度。如果设置为0,则所有查询都会被记录,这在极端调试场景下有用,但会带来大量磁盘写入,生产环境应避免。为了让配置在数据库重启后依然有效,应当修改MySQL配置文件(如my.cnf或my.ini):
[mysqld] slow_query_log = 1 slow_query_log_file = /var/lib/mysql/slow.log long_query_time = 0.5 log_queries_not_using_indexes = 1
其中log_queries_not_using_indexes表示即便查询未超过时间阈值,只要没有使用索引也记入日志。这个选项能帮助我们提前发现潜在的全表扫描问题。修改配置文件后需重启MySQL服务才能生效,使用systemctl restart mysqld或对应平台的命令即可。
二、日志内容结构解析
一条典型的慢查询日志包含查询时间、执行耗时、锁等待时间、发送行数、扫描行数以及具体的SQL文本。理解这些字段是后续分析的基础。下面是一段经过简化的日志样例:
# Time: 2023-08-12T10:22:01.123456Z # User@Host: app[app] @ 192.168.0.1 [] # Query_time: 2.341234 Lock_time: 0.000123 # Rows_sent: 10 Rows_examined: 982341 SELECT * FROM orders WHERE user_id = 123 AND status = 'paid' ORDER BY created_at DESC LIMIT 10;
Query_time是语句从开始到结束的总耗时,Lock_time是等待表锁的时间。如果Lock_time接近Query_time,说明瓶颈在锁竞争而非SQL本身。Rows_examined代表存储引擎扫描的行数,而Rows_sent是返回给客户端的行数。当Rows_examined远大于Rows_sent时,通常意味着缺少合适的索引或写了不够精确的过滤条件。
除了上述基础字段,MySQL还可在日志中记录更详细的信息,例如通过log_slow_extra输出线程ID、事务ID等。对于复杂系统,建议结合应用层的trace_id,在SQL注释中带上标识,这样能从慢日志反查到具体业务请求。例如:SELECT /* trace_abc123 */ * FROM orders ...,这种写法不影响执行计划,却极大方便了跨系统排查。
三、慢查询的分析工具与方法
直接用人眼翻阅原始日志效率极低,尤其当日志达到几万行时。MySQL自带了mysqldumpslow工具,可以按不同维度汇总相似的SQL。常见用法如下:
# 按平均查询时间排序,取前10条 mysqldumpslow -s at -t 10 /var/lib/mysql/slow.log # 按总耗时排序 mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log # 不将数字和字符串抽象为N和S,方便看具体值 mysqldumpslow -a -s c /var/lib/mysql/slow.log
-s参数指定排序方式,c是执行次数,t是总耗时,at是平均耗时。通过归类,我们能快速找到“执行次数多且平均慢”的语句,这类语句往往比偶发性的超级慢查询更值得优化。另一个更强大的开源工具是Percona的pt-query-digest,它能生成包含响应时间分布、索引建议的详细报告。
在分析时,不要只盯着Query_time。一条SQL如果每次只慢0.1秒但每秒执行200次,对数据库的压力远大于一条偶尔跑5秒的报表查询。因此要把Rows_examined和执行频率放在一起看。优化手段通常包括:为WHERE和ORDER BY字段建立联合索引、避免SELECT *、把子查询改写为JOIN、利用覆盖索引减少回表。下例展示了一个缺少索引的慢查询及其优化:
-- 原始慢SQL,orders表在user_id上无索引 SELECT id, amount FROM orders WHERE user_id = 123 AND status = 'paid'; -- 增加联合索引后的DDL ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
建立索引后,存储引擎可直接定位到匹配的行,Rows_examined从全表量级降到个位数,查询耗时会呈数量级下降。但要注意索引并非越多越好,写操作会因为维护索引变慢,需权衡读写比例。
四、常见误区与注意事项
不少团队在开启慢查询日志后,把long_query_time设得过大,比如5秒,结果大量1到2秒的“中速查询”被漏掉,用户依然反馈卡顿。一般Web接口建议阈值设在0.1到0.5秒之间,这样才能捕获影响体验的语句。另外,开启log_queries_not_using_indexes时要小心,若某小表本就只有几百行,全表扫描比走索引更快,这类记录会污染日志,可配合min_examined_row_limit忽略扫描行数过少的查询。
还有一点容易被忽视:慢查询日志本身会消耗磁盘IO。在高并发写入场景,建议将日志放到独立磁盘,并定期用脚本归档清理。对于云数据库,通常控制台已提供慢查询分析面板,底层也是同一套机制,但省去了自建采集的麻烦。无论用哪种方式,核心逻辑不变,就是先让慢语句显形,再用索引和执行计划(EXPLAIN)验证优化效果,形成闭环。
五、结合EXPLAIN做深度验证
从日志里拿到可疑SQL后,应在测试环境用EXPLAIN查看执行计划。重点观察type列,若出现ALL表示全表扫描;key列显示实际使用的索引;rows列是预估扫描行数,应与日志中的Rows_examined对照。示例如下:
EXPLAIN SELECT id, amount FROM orders WHERE user_id = 123 AND status = 'paid';
如果key为NULL且type为ALL,证明索引未生效,需检查字段类型是否一致(如字符串字段用了数字比较)、是否对字段使用了函数导致索引失效。把EXPLAIN结果和慢日志字段交叉比对,能避免凭直觉优化而忽略真实执行路径,从而让每一次调整都落在真正的瓶颈上。
slow_query_logSQL优化查询分析修改时间:2026-08-01 19:33:43