在 MySQL 数据库管理中,慢查询日志(Slow Query Log)是一个至关重要的工具,它能够帮助我们识别执行时间较长的 SQL 查询,从而为性能优化提供有力支持。
一、慢查询日志原理
1. 记录条件
MySQL 慢查询日志记录执行时间超long_query_time
秒,且检查行数达min_examined_row_limit
的 SQL 语句。long_query_time
默认值 10,最小值 0,支持微秒级精度。如设为 2 秒,满足条件的慢查询将被记录。
另外,默认情况下,管理语句(如ALTER TABLE
、ANALYZE TABLE
等)不会被记录,未使用索引进行查找的查询也不会被记录。但我们可以通过log_slow_admin_statements
和log_queries_not_using_indexes
这两个系统变量来改变这种行为。
2. 记录时机
MySQL 服务器在查询执行完毕且所有锁都已释放后,才会将该查询写入慢查询日志。这就导致日志记录的顺序可能与查询执行的顺序不一致。例如,查询 A 先开始执行,但查询 B 先执行完毕并释放锁,那么查询 B 可能会先被记录到慢查询日志中。
3. 相关系统变量
slow_query_log
:控制慢查询日志开关,默认禁用。可通过--slow_query_log({0|1})
指定初始状态,或运行时用SET GLOBAL slow_query_log = {0|1}
控制。slow_query_log_file
:指定日志文件名,默认host_name-slow.log
。可通过--slow_query_log_file
指定路径,运行时用SET GLOBAL
修改文件名并切换文件。log_output
:指定日志输出为FILE
、TABLE
或FILE,TABLE
,设为表时需注意文件打开数限制。log_slow_admin_statements
:启用后记录ALTER TABLE
等慢管理语句。log_queries_not_using_indexes
:记录未用索引查询(表行数≥2 时),log_throttle_queries_not_using_indexes
可限制记录速率。log_timestamps
:控制日志文件时间戳时区,不影响日志表,表数据可通过函数或会话变量转换时区。log_slow_replica_statements
(8.0.26+)/log_slow_slave_statements
(8.0.26 前):默认副本不记录复制慢查询,仅binlog_format
为statement
或mixed
(语句格式)时生效。
二、慢查询日志使用方法
1. 开启慢查询日志
配置文件方式
在 MySQL 的配置文件(通常是my.cnf
或my.ini
)中,找到[mysqld]
部分,添加或修改以下配置:
slow\_query\_log = 1
slow\_query\_log\_file = /path/to/your/slow-query.log
long\_query\_time = 2
log\_queries\_not\_using\_indexes = 1
上述配置中,slow_query_log = 1
开启慢查询日志;slow_query_log_file
指定日志文件路径;long_query_time = 2
将慢查询的时间阈值设置为 2 秒;log_queries_not_using_indexes = 1
表示记录未使用索引的查询。修改完成后,重启 MySQL 服务使配置生效。
命令行方式
也可以在 MySQL 运行时通过 SQL 命令来开启慢查询日志:
SET GLOBAL slow\_query\_log = 1;
SET GLOBAL slow\_query\_log\_file = '/path/to/your/slow-query.log';
SET GLOBAL long\_query\_time = 2;
SET GLOBAL log\_queries\_not\_using\_indexes = 1;
这种方式无需重启 MySQL 服务,但在服务器重启后设置会失效。如果要永久生效,还是建议修改配置文件。
2. 查看慢查询日志
文件方式查看
如果将慢查询日志输出到文件,可以使用文本编辑器直接打开日志文件查看。日志文件的每一条记录通常包含以下信息:
例如:
# Time: 2023 - 05 - 10T14:30:15.123456Z
# User@Host: root(root) @ localhost () Id: 10
# Query\_time: 3.567890
Lock_time: 0.000123
Rows_sent: 100
Rows_examined: 10000
SET timestamp = 1683714615;
SELECT \* FROM large\_table WHERE some\_column = 'value';
其中,Time
是查询执行的时间;User@Host
是执行查询的用户和主机信息;Query_time
是查询执行时间;Lock_time
是锁等待时间;Rows_sent
是返回的行数;Rows_examined
是检查的行数;SET timestamp
用于设置查询开始执行时的时间戳;最后是具体的 SQL 查询文本。
表方式查看
如果将log_output
设置为TABLE
,MySQL 会将慢查询日志记录到mysql.slow_log
表中。可以通过以下 SQL 查询来查看慢查询日志:
SELECT \* FROM mysql.slow\_log;
该表的结构大致如下:
*************************** 1. row ***************************
start_time: 2025-05-15 10:30:45.123456
user_host: root[root] @ localhost []
query_time: 00:00:02.143567
lock_time: 00:00:00.012345
rows_sent: 100
rows_examined: 1000000
db: ecom
last_insert_id: 0
insert_id: 0
server_id: 1
sql_text: SELECT * FROM orders WHERE order_date < '2023-01-01' ORDER BY total_amount DESC
thread_id: 12345
*************************** 2. row ***************************
start_time: 2025-05-15 10:35:12.678901
user_host: app_user[app] @ 192.168.1.50 []
query_time: 00:00:05.891234
lock_time: 00:00:00.034567
rows_sent: 5
rows_examined: 5000000
db: ecom
last_insert_id: 0
insert_id: 0
server_id: 1
sql_text: SELECT COUNT(*) FROM products JOIN inventory ON products.id = inventory.product_id
thread_id: 12346
*************************** 3. row ***************************
start_time: 2025-05-15 10:42:30.456789
user_host: root[root] @ localhost []
query_time: 00:00:10.234567
lock_time: 00:00:00.056789
rows_sent: 0
rows_examined: 2000000
db: test
last_insert_id: 0
insert_id: 0
server_id: 1
sql_text: UPDATE users SET status = 'inactive' WHERE last_login < '2022-12-31'
thread_id: 12347
分析重点
查询耗时:
- query_time: 00:00:10.23(第三条记录)属于明显慢查询
- 对比 lock_time 和 query_time,判断锁等待是否是瓶颈(如第二条记录 lock_time 占比约 0.5%,影响较小)
扫描效率:
- 第一条记录 rows_examined(100 万)与 rows_sent(100)比例悬殊,需添加索引优化
- 第二条记录扫描 500万行仅返回 5 行,可能存在全表扫描问题
三、慢查询日志分析工具
1. mysqldumpslow
mysqldumpslow
是 MySQL 自带的一个命令行工具,用于解析和汇总慢查询日志文件的内容。它可以帮助我们快速了解慢查询日志中的关键信息,例如:
mysqldumpslow /usr/local/mysq/logl/slow-query.log
# /usr/local/mysq/logl/slow-query.log 根据实际存放位置来写
默认情况下,mysqldumpslow
会对查询进行分组,将相似的查询(除了数字和字符串数据值的具体值不同)归为一组,并在显示摘要输出时将这些值抽象为n
和's'
。例如:
Reading mysql slow query log from /usr/local/mysql/data/mysqld80-slow.log
Count: 1 Time=4.32s (4s) Lock=0.00s (0s) Rows=0.0 (0), root(root) @ localhost
insert into t2 select \* from t1
Count: 3 Time=2.53s (7s) Lock=0.00s (0s) Rows=0.0 (0), root(root) @ localhost
insert into t2 select \* from t1 limit n
上述输出中,Count
表示该类查询出现的次数;Time
表示总执行时间和平均执行时间;Lock
表示总锁等待时间和平均锁等待时间;Rows
表示返回的总行数和平均行数。
mysqldumpslow
还支持许多选项,例如:
-s
:指定排序方式。例如,-s r
按返回的行数排序,-s c
按查询出现的次数排序,-s t
按查询时间排序。-t
:指定显示的查询数量。例如,-t 10
表示只显示前 10 条查询。-g
:用于过滤查询,只显示匹配指定模式的查询。例如,-g "SELECT.*FROM users"
只显示涉及users
表的SELECT
查询。
2. pt-query-digest
pt-query-digest
是 Percona 提供的一个更为强大的慢查询日志分析工具。它不仅可以分析 MySQL 的慢查询日志,还能对查询执行计划、索引使用情况等进行深入分析,并生成详细的报告。要使用pt-query-digest
,首先需要安装 Percona Toolkit:
sudo apt-get install percona-toolkit
安装完成后,可以使用以下命令来分析慢查询日志:
pt-query-digest /path/to/your/slow-query.log
#/path/to/your/ 为实际存放位置
案例
# 20ms user time, 120ms system time, 34.79M rss, 102.62M vsz
# Current date: Tue Jun 24 15:51:51 2025
# Hostname: localhost.localdomain
# Files: /var/log/mysql/mysql_slow.log
# Overall: 1 total, 1 unique, 0 QPS, 0x concurrency ______________________
# Time range: all events occurred at 2025-06-23T11:17:18
# Attribute total min max avg 95% stddev median
# ============ ======= ======= ======= ======= ======= ======= =======
# Exec time 8s 8s 8s 8s 8s 0 8s
# Lock time 2ms 2ms 2ms 2ms 2ms 0 2ms
# Rows sent 0 0 0 0 0 0 0
# Rows examine 0 0 0 0 0 0 0
# Query size 18 18 18 18 18 0 18
1、关键指标
执行时间(Exec time)
- 关注点:长时间执行可能由表数据量大、锁冲突、磁盘 IO 瓶颈等原因导致。
锁等待时间(Lock time)
- 关注点:若锁等待时间占比高,可能存在事务并发冲突(如之前死锁案例中的情况)。
扫描行数(Rows examine)与发送行数(Rows sent)
- 关注点
Rows examine
高表示全表扫描(如无索引时),需优化索引;Rows sent
低但Rows examine
高,说明查询返回结果少但扫描量大,索引缺失问题显著。
- 关注点
查询类型与具体语句
pt-query-digest
的输出结果包含丰富的信息,例如:
按查询执行时间、锁等待时间、返回行数等维度排序的查询列表。
每个查询的详细执行计划,包括索引使用情况、扫描行数等。
针对每个查询的优化建议,例如添加索引、重写查询等。
通过分析pt-query-digest
的输出,我们可以更全面地了解数据库的性能问题,并采取针对性的优化措施。
四、案例分析
案例一:查询执行时间过长
在某电商平台的实际运营中,随着业务规模的扩大,用户频繁反馈商品列表页加载迟缓。技术团队迅速开启慢查询日志,并将long_query_time
阈值设为 2 秒。经过 48 小时的数据采集,发现了一条典型慢查询记录:
\# Time: 2023-06-01T15:20:30.567890Z
\# User@Host: web_user(web_server) @ 192.168.1.100 () Id: 50
# Query_time: 3.567890
Lock_time: 0.000123
Rows_sent: 100
Rows_examined: 100000
SET timestamp = 1685632830;
SELECT * FROM products WHERE category = 'electronics' AND price > 500;
通过EXPLAIN
执行计划分析,发现该查询触发了全表扫描:在包含 10 万条记录的products
表中,由于category
和price
列缺乏有效索引,数据库引擎无法快速定位目标数据,导致单条查询耗时长达 3.5 秒。从索引原理来看,单列索引无法同时满足多条件筛选需求,而复合索引能构建高效的 B + 树数据结构,显著提升查询性能。
优化方案采用覆盖索引策略,创建联合索引:
CREATE INDEX idx_category_price ON products (category, price);
优化后进行压力测试,相同查询场景下响应时间降至 120 毫秒,配合前端缓存机制,页面加载速度提升 80% 以上。同时需要注意,复合索引遵循 “最左前缀原则”,查询条件必须包含category
列才能有效利用该索引。
案例二:未使用索引的查询
某数据分析系统在处理订单统计时,出现响应延迟问题。通过慢查询日志定位到高频慢查询:
\# Time: 2023-06-02T10:15:45.234567Z
\# User@Host: analytics\_user(analytics\_server) @ 192.168.1.101 () Id: 60
# Query_time: 2.123456
Lock_time: 0.000234
Rows_sent: 500
Rows_examined: 50000
SET timestamp = 1685686545;
SELECT \* FROM orders WHERE order\_date BETWEEN '2023-01-01' AND '2023-06-01';
该查询需要扫描 5 万条订单记录,而order_date
列缺失索引导致全表扫描。虽然日期范围查询理论上可以使用索引,但无序的扫描方式严重影响性能。
采用单列索引优化方案:
CREATE INDEX idx_order_date ON orders (order_date);
优化后配合分区表技术(按月份对订单表分区),相同查询响应时间缩短至 350 毫秒。值得注意的是,对于频繁写入的表,过多索引会增加写操作开销,建议通过SHOW INDEX
命令监控索引使用情况,必要时进行索引重建或合并。同时,对于范围查询场景,覆盖索引比普通索引能获得更好的性能表现。