慢查询日志
导学
执行计划分析单条 SQL,但生产环境中你需要知道"哪些 SQL 拖慢了系统"。慢查询日志(Slow Query Log)是 MySQL 提供的性能诊断工具,自动记录执行时间超过阈值的 SQL。
定义
慢查询日志:MySQL 记录执行时间超过指定阈值(long_query_time)的 SQL 语句的日志文件。开启后,所有"慢 SQL"都会被记录,便于后续分析和优化。
配置慢查询日志
示例:查看和配置慢查询日志
-- 查看当前慢查询日志配置
SHOW VARIABLES LIKE 'slow_query%';
当前配置状态:
| Variable_name | Value |
|---|---|
| slow_query_log | OFF |
| slow_query_log_file | /var/lib/mysql/hostname-slow.log |
-- 查看慢查询时间阈值
SHOW VARIABLES LIKE 'long_query_time';
| Variable_name | Value |
|---|---|
| long_query_time | 10.000000 |
结果解读:
slow_query_log = OFF:慢查询日志默认关闭long_query_time = 10:默认超过 10 秒的查询才被认为是慢查询
开启慢查询日志
-- 临时开启慢查询日志(需 SUPER 权限,重启后失效)
SET GLOBAL slow_query_log = 'ON';
-- 设置慢查询日志文件路径
SET GLOBAL slow_query_log_file = '/var/lib/mysql/slow.log';
-- 设置慢查询时间阈值(超过 1 秒视为慢查询)
SET GLOBAL long_query_time = 1;
-- 开启记录未使用索引的查询(强烈建议开启)
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- 验证配置是否生效
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
操作后的配置状态:
| Variable_name | Value |
|---|---|
| slow_query_log | ON |
| slow_query_log_file | /var/lib/mysql/slow.log |
| Variable_name | Value |
|---|---|
| long_query_time | 1.000000 |
结果解读:
- 现在执行时间超过 1 秒的查询会被记录到
/var/lib/mysql/slow.log log_queries_not_using_indexes = ON表示即使查询很快,只要没走索引也会被记录
my.cnf 永久配置
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
结果解读:
- 修改
my.cnf后需要重启 MySQL 服务才能生效 - 永久配置适合生产环境,避免重启后配置丢失
日志内容示例
示例:构造一条慢查询
-- 准备测试数据:创建一个大表
CREATE TABLE slow_test_table (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status INT DEFAULT 0
);
-- 插入大量数据(使用存储过程或批量插入)
INSERT INTO slow_test_table (name, description, status)
SELECT
CONCAT('user_', seq),
CONCAT('description_', seq, '_', REPEAT('x', 100)),
FLOOR(RAND() * 10)
FROM (
SELECT @row := @row + 1 AS seq
FROM (SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t1,
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t2,
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t3,
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t4,
(SELECT @row := 0) t5
) numbers
WHERE seq <= 100000;
当前数据状态:
- 表中已有 100,000 行数据
- 没有为
status列创建索引
-- 执行一条可能很慢的查询(全表扫描)
SELECT * FROM slow_test_table WHERE status = 5 ORDER BY created_at DESC LIMIT 100;
结果解读:
- 这条查询需要全表扫描 100,000 行,过滤出
status = 5的记录,然后排序 - 如果执行时间超过
long_query_time(1秒),会被记录到慢查询日志
慢查询日志格式
日志文件中会记录类似以下内容:
# Time: 2026-06-10T15:30:01.123456Z
# User@Host: app_user[app_user] @ [192.168.1.100]
# Query_time: 2.345678 Lock_time: 0.000123 Rows_sent: 100 Rows_examined: 100000
SET timestamp=1718011801;
SELECT * FROM slow_test_table WHERE status = 5 ORDER BY created_at DESC LIMIT 100;
关键字段解析:
| 字段 | 含义 |
|---|---|
Time | 查询执行的时间戳 |
User@Host | 执行查询的用户和来源 IP |
Query_time | SQL 总执行时间(秒) |
Lock_time | 等待锁的时间 |
Rows_sent | 返回给客户端的行数 |
Rows_examined | 扫描的行数(越大越需优化) |
结果解读:
Query_time: 2.345678:这条查询执行了约 2.35 秒Rows_sent: 100:只返回了 100 行给客户端Rows_examined: 100000:但扫描了 100,000 行Rows_examined远大于Rows_sent,说明过滤条件效率极低,是优化的重点信号
分析慢查询日志
方法一:mysqldumpslow(官方工具)
# 按出现次数排序,显示前 10
mysqldumpslow -s c -t 10 /var/lib/mysql/slow.log
# 按执行时间排序
mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log
# 按平均执行时间排序
mysqldumpslow -s at -t 10 /var/lib/mysql/slow.log
结果解读:
-s c:按出现次数(count)排序,找出执行频率最高的慢查询-s t:按总执行时间排序,找出最耗时的慢查询-s at:按平均执行时间排序-t 10:只显示前 10 条
方法二:pt-query-digest(Percona Toolkit,推荐)
# 生成详细的慢查询分析报告
pt-query-digest /var/lib/mysql/slow.log > slow_report.txt
# 只分析最近 1 小时的日志
pt-query-digest --since="1h" /var/lib/mysql/slow.log
# 过滤只包含某张表的查询
pt-query-digest --filter '$event->{arg} =~ m/slow_test_table/i' /var/lib/mysql/slow.log
pt-query-digest 会输出:
- 总体统计(执行次数、总时间、平均时间)
- 慢查询排行榜(按执行时间或出现频率)
- 每条慢查询的详细分析(执行时间分布、rows examined 等)
示例输出解读:
# Profile
# Rank Query ID Response time Calls R/Call V/M Item
# ==== ================== ============= ===== ====== ===== ==========
# 1 0x1234567890ABCDEF 45.2345 50.0% 100 0.4523 0.01 SELECT slow_test_table
# 2 0xABCDEF1234567890 22.5678 25.0% 50 0.4514 0.02 SELECT accounts
结果解读:
Rank 1的查询占总响应时间的 50%,执行了 100 次,平均每次 0.45 秒- 这是最需要优先优化的查询
SQL 示例
场景一:动态调整慢查询阈值
-- 查看当前阈值
SHOW VARIABLES LIKE 'long_query_time';
当前数据状态:
| Variable_name | Value |
|---|---|
| long_query_time | 1.000000 |
-- 临时调整为 0.5 秒(用于排查问题时捕获更多查询)
SET GLOBAL long_query_time = 0.5;
-- 验证修改
SHOW VARIABLES LIKE 'long_query_time';
操作后的状态:
| Variable_name | Value |
|---|---|
| long_query_time | 0.500000 |
-- 排查完成后恢复为 1 秒
SET GLOBAL long_query_time = 1;
结果解读:
- 调低阈值可以捕获更多"潜在慢查询",帮助发现性能隐患
- 注意:
long_query_time对已有连接不立即生效,新连接才会使用新阈值
场景二:验证慢查询日志是否记录
-- 确保慢查询日志已开启
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.1;
-- 执行一个故意很慢的查询:使用 SLEEP 函数
SELECT SLEEP(0.5), 'this is a slow query' AS msg;
查询结果:
| SLEEP(0.5) | msg |
|---|---|
| 0 | this is a slow query |
结果解读:
SLEEP(0.5)会让查询暂停 0.5 秒- 因为
long_query_time = 0.1,这条查询会被记录到慢查询日志 - 可以查看日志文件验证是否记录成功
场景三:分析未使用索引的查询
-- 确保开启记录未使用索引的查询
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- 执行一个未走索引的查询(假设 status 列没有索引)
SELECT COUNT(*) FROM slow_test_table WHERE status = 3;
查询结果:
| COUNT(*) |
|---|
| 约 10000 |
结果解读:
- 这条查询进行了全表扫描,没有使用任何索引
- 即使执行时间很快(比如 0.05 秒),也会被记录到慢查询日志(因为开启了
log_queries_not_using_indexes) - 这是发现"潜在性能问题"的重要手段
优化方向:
-- 为 status 列添加索引
CREATE INDEX idx_status ON slow_test_table(status);
-- 再次执行相同查询
SELECT COUNT(*) FROM slow_test_table WHERE status = 3;
优化后的结果:
- 查询会使用
idx_status索引,扫描行数从 100,000 降到约 10,000 - 不再出现在"未使用索引"的慢查询日志中
场景四:使用 performance_schema 分析慢查询
MySQL 5.7 还可以通过 performance_schema 分析查询性能:
-- 查看 statements_summary_by_digest 表(需提前开启 performance_schema)
SELECT
DIGEST_TEXT AS query,
COUNT_STAR AS exec_count,
SUM_TIMER_WAIT/1000000000000 AS total_latency_sec,
AVG_TIMER_WAIT/1000000000000 AS avg_latency_sec,
SUM_ROWS_EXAMINED AS rows_examined,
SUM_ROWS_SENT AS rows_sent
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 5;
示例输出:
| query | exec_count | total_latency_sec | avg_latency_sec | rows_examined | rows_sent |
|---|---|---|---|---|---|
SELECT * FROM slow_test_table WHERE status = ? ORDER BY created_at DESC LIMIT ? | 100 | 45.2345 | 0.4523 | 10000000 | 10000 |
结果解读:
exec_count = 100:这条查询被执行了 100 次total_latency_sec = 45.23:总耗时 45.23 秒rows_examined = 10000000:总共扫描了 1000 万行rows_sent = 10000:只返回了 1 万行rows_examined远大于rows_sent,说明索引优化空间很大
常见误区
| 误区 | 正解 |
|---|---|
| 慢查询日志默认开启 | 默认关闭,需要手动开启。 |
long_query_time 对已有连接立即生效 | 只对新连接生效,已有连接需重连。 |
| 所有慢查询都需要优化 | 有些查询业务上本来就慢(如大数据量报表),应区分场景。 |
面试考点
Q:慢查询日志的开启步骤?
SET GLOBAL slow_query_log = 'ON';2. 设置日志路径slow_query_log_file;3. 设置阈值long_query_time;4. 建议开启log_queries_not_using_indexes。
Q:Rows_examined 和 Rows_sent 的区别?
Rows_examined是 MySQL 扫描的行数;Rows_sent是返回给客户端的行数。两者差距越大,说明过滤条件效率越低(扫描了大量数据却只返回少量结果)。
Q:pt-query-digest 和 mysqldumpslow 的区别?
mysqldumpslow是 MySQL 自带的简单工具;pt-query-digest是 Percona Toolkit 的高级工具,输出更详细,支持更复杂的过滤和分析。
小结
- 慢查询日志记录执行时间超过阈值的 SQL
- 建议开启
log_queries_not_using_indexes,捕获未走索引的查询 - 用
pt-query-digest分析日志,找出最需要优化的 SQL - 关注
Rows_examined,扫描行数远大于返回行数是优化信号
下一章引子:发现了慢查询,接下来学习如何通过索引优化来解决问题。