三招精准定位慢SQL
在高并发、大数据量的业务场景中,SQL性能直接影响系统响应速度与用户体验。慢SQL不仅消耗大量数据库资源,还可能引发连锁反应,导致服务雪崩。因此,快速、精准地定位慢SQL是数据库性能优化的第一步。那么,怎么定位慢SQL?下面,以MySQL为例,我们从慢查询日志、Performance Schema和sys schema三个层次,系统介绍如何由浅入深地识别并分析慢SQL。仅供参考。
一、慢查询日志:初步定位谁慢了(耗时SQL列表)
慢查询日志是MySQL默认提供的,记录执行时间超过指定阈值的SQL语句,包含:执行时间、锁等待时间、扫描行数等信息,帮助我们识别执行效率低下的查询。
1、3步开启慢查询日志
MySQL默认关闭慢查询日志(避免性能损耗),需要我们手动开启,支持临时开启(重启失效)和永久开启(实际业务场景推荐)两种方式。
第一步:查看当前配置
我们登录MySQL客户端(命令行或Navicat),执行以下命令,了解当前慢查询日志的状态:
-- 查看慢查询日志是否开启(ON=开启,OFF=关闭)
show variables like '%slow_query_log%';
-- 查看慢查询阈值(单位:秒,默认10s)
show variables like 'long_query_time';
-- 查看日志输出方式(FILE=文件,TABLE=系统表)
show variables like '%log_output%';
-- 查看未走索引的SQL是否记录(ON=记录,OFF=不记录)
show variables like 'log_queries_not_using_indexes';
-- 查看慢查询日志存储路径
show variables like 'slow_query_log_file';
执行结果示例(默认未开启状态):
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
实际业务中,long_query_time我们建议设为0.5~1秒,而非默认的10秒,以便更早发现性能隐患。可通过SET SESSION long_query_time = 0.5;在当前会话立即生效(用于调试),但是SET GLOBAL对已有连接不生效,需要新连接才应用。
第二步:临时开启(应急排查)
如果不需要重启MySQL,我们可通过set global命令临时开启,适合线上应急排查:
-- 开启慢查询日志
set global slow_query_log = ON;
-- 设置阈值为2秒(新连接生效,旧连接需重新连接)
set global long_query_time = 2;
-- 记录未走索引的SQL(即使未超阈值)
set global log_queries_not_using_indexes = ON;
-- 设置日志输出方式为文件(优先选择,比表存储高效)
set global log_output = 'FILE';
-- 刷新当前会话配置,使新的全局慢查询阈值立即对当前连接生效
flush logs;
set session long_query_time = global.long_query_time;
⚠️ 注意:log_queries_not_using_indexes = ON可能产生大量日志(尤其高频小查询),我们建议仅在排查期开启,或配合 min_examined_row_limit使用(如:设为1000,只记录扫描行数超过1000的无索引查询)。
第三步:永久开启(实际业务场景)
临时配置重启后失效,实际业务场景我们需要修改MySQL配置文件(my.cnf或my.ini):
-
Linux系统:配置文件路径通常为 /etc/my.cnf或/etc/mysql/my.cnf; -
Windows系统:配置文件路径通常为 MySQL安装目录/my.ini。
在[mysqld]节点下,我们添加以下配置:
[mysqld]
# 开启慢查询日志
slow_query_log = 1
# 慢查询日志文件路径(需要确保MySQL有写入权限)
slow_query_log_file = /var/lib/mysql/mysql-slow.log
# 慢查询阈值(单位:秒)
long_query_time = 2
# 记录未使用索引的SQL(我们建议开启,提前发现无索引SQL)
log_queries_not_using_indexes = 1
# 仅当扫描行数超过此值时才记录无索引查询(可选,减轻日志压力)
min_examined_row_limit = 1000
# 记录执行耗时超过阈值的管理员语句(如:ALTER TABLE等DDL)(可选)
log_slow_admin_statements = 1
# 记录慢的优化器执行计划(可选)
log_slow_slave_statements = 1
# 日志输出方式(FILE=文件,TABLE=mysql.slow_log表,可同时设置)
log_output = FILE
配置完成后,我们重启MySQL,使配置生效:
-
Linux: systemctl restart mysqld或service mysqld restart; -
Windows:在服务中,我们找到MySQL,右键重启。
2、慢查询日志解读
慢查询日志的每条记录都包含执行时间、用户信息、SQL内容等字段,格式如下:
# Time: 2024-05-20T15:30:45.123456Z
# User@Host: app_user[app_user] @ 192.168.1.100 [192.168.1.100] Id: 1234
# Query_time: 3.800000 Lock_time: 0.000200 Rows_sent: 150 Rows_examined: 120000
# Rows_affected: 0 Bytes_sent: 28500
SET timestamp=1684606245;
select * from order_info where user_id = 567 and create_time between '2024-01-01' and '2024-06-01';
解读:作为优化依据
-
Time:SQL执行的时间戳; -
User@Host:执行SQL的用户和客户端IP(用于定位业务来源); -
Query_time:SQL实际执行时间(3.8秒,超过2秒阈值); -
Lock_time:SQL等待表锁/行锁的时间(0.0002秒,说明不是锁问题); -
Rows_sent:返回给客户端的行数(150行); -
Rows_examined:MySQL扫描的行数(12万行,扫描行数远远大于返回行数,估计没有走索引或索引失效); -
Bytes_sent:返回数据的字节数(28500字节≈28KB,不是网络瓶颈); -
最后一行:具体的慢SQL语句。
3、慢查询日志分析工具:mysqldumpslow与pt-query-digest
当慢查询日志文件过大(如:几十MB甚至GB级),我们手动打开查找,效率极低,这时需要借助工具进行筛选和聚合分析。
工具1:mysqldumpslow(MySQL自带,轻量)
mysqldumpslow是MySQL内置的慢查询日志分析工具,能从慢查询日志中筛选并呈现具有代表性、对性能影响较大的SQL语句。它支持按执行时间、扫描行数、执行次数等指标排序,帮助我们快速定位和优化有性能问题的查询。
常用命令示例:
# 1.按执行时间排序,取前10条最慢的SQL
mysqldumpslow -s t -t 10 /var/lib/mysql/mysql-slow.log
# 2.按扫描行数排序,取前10条(扫描行数越多,效率越低)
mysqldumpslow -s r -t 10 /var/lib/mysql/mysql-slow.log
# 3.按执行次数排序,取前10条(高频慢SQL危害更大)
mysqldumpslow -s c -t 10 /var/lib/mysql/mysql-slow.log
# 4.筛选包含order_info表的慢SQL
mysqldumpslow -g 'order_info' /var/lib/mysql/mysql-slow.log
# 5.筛选用户app_user执行的慢SQL(结合grep)
grep -i 'User@Host.*app_user' /var/lib/mysql/mysql-slow.log | mysqldumpslow -s t -t 10 -
# 6.输出详细信息(包括执行时间、扫描行数等)
mysqldumpslow -v /var/lib/mysql/mysql-slow.log
工具2:pt-query-digest(Percona Toolkit,功能强大)
mysqldumpslow支持基础筛选,复杂场景下,我们建议使用Percona Toolkit中的pt-query-digest。它能聚合相似SQL(如:user_id=567和user_id=890视为同一类SQL)、分析执行频率、计算平均耗时等。
安装步骤(Linux为例):
# 安装Percona Toolkit
yum install percona-toolkit -y
# 验证安装
pt-query-digest --version
常用命令示例:
# 1.分析慢查询日志,生成详细报告
pt-query-digest /var/lib/mysql/mysql-slow.log > slow_report.txt
# 2.分析最近1小时的慢SQL
pt-query-digest --since=1h /var/lib/mysql/mysql-slow.log > slow_report_1h.txt
# 3.只分析order_info表的慢SQL
pt-query-digest --filter '$event->{table} eq "order_info"' /var/lib/mysql/mysql-slow.log > slow_report_order.txt
# 4.分析MySQL的general log(全量日志)
pt-query-digest /var/lib/mysql/general.log > general_report.txt
# 5.指定时间窗口(精确到秒)
pt-query-digest --since '2026-01-29 14:00:00' --until '2026-01-29 15:00:00' /var/lib/mysql/mysql-slow.log
# 6.直接分析mysql.slow_log表中的慢SQL(当log_output='TABLE'时非常方便)
pt-query-digest h=127.0.0.1,u=root,p=your_password,D=mysql,t=slow_log > slow_report_table.txt
报告解读:pt-query-digest生成的报告分为摘要和详细分析两部分,摘要部分我们要关注:
-
Total:总执行时间、总执行次数; -
Avg:平均执行时间; -
95%:95%的SQL执行时间(比平均时间更能反映真实性能); -
RowsExam:平均扫描行数; -
Query:SQL模板(相似SQL聚合后的模板)。
提醒:我们不要只关注“最慢的一条SQL”,高频中等慢SQL(如:200ms × 1000 QPS)对系统整体负载的影响可能远远大于单条5秒SQL。
二、Performance Schema:深度监控执行流程(深入分析为什么慢?如:锁等待、IO瓶颈等)
慢查询日志只能记录执行完成且超过阈值的SQL(初步定位 谁慢了),无法监控执行中但是没有超时的SQL或SQL卡在哪个步骤。此时,我们需要借助Performance Schema:MySQL 5.5+引入的性能监控引擎,能实时追踪SQL执行的每个环节(如:连接建立、语法解析、执行计划生成)。
1、开启Performance Schema
MySQL 5.6+默认开启Performance Schema,我们可通过以下命令确认:
show variables like 'performance_schema';
如果值为OFF,我们需要在my.cnf中开启并重启MySQL:
[mysqld]
performance_schema = ON
注意:Performance Schema在高并发场景下可能带来5%~10%的性能开销。如果系统极度敏感,可仅开启必要组件:
UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES'
WHERE NAME LIKE '%statements%' OR NAME LIKE '%waits%';
2、常用监控场景示例
场景1:查看当前正在执行的慢SQL
当系统卡顿,但是慢查询日志未记录(SQL未执行完成),我们可通过以下SQL查看正在执行的SQL及状态:
select
p.id as process_id,
u.user,
p.host,
p.db,
p.command,
p.time as execution_time, -- 已执行时间(秒)
p.state, -- 执行状态(如:Sending data、Sorting result)
p.info as sql_text -- SQL内容
from information_schema.processlist p
left join mysql.user u on p.user = u.user
where p.time > 10 -- 筛选执行超过10秒的SQL
and p.command != 'Sleep'; -- 排除睡眠连接
场景2:分析SQL执行各阶段耗时
通过performance_schema.events_statements_summary_by_digest表,我们可查看SQL在解析、优化、执行等阶段的耗时(使用FORMAT_PICO_TIME()函数自动转换为易读单位):
select
digest_text as sql_text, -- SQL模板
count_star as execute_count, -- 执行次数
FORMAT_PICO_TIME(sum_timer_parse) as parse_time, -- 解析耗时(自动转换,如:1.23ms)
FORMAT_PICO_TIME(sum_timer_optimize) as optimize_time, -- 优化耗时
FORMAT_PICO_TIME(sum_timer_execute) as execute_time, -- 执行耗时
sum_rows_examined as total_rows_examined -- 总扫描行数
from performance_schema.events_statements_summary_by_digest
order by sum_timer_execute desc
limit 10;
场景3:查看索引使用情况
通过performance_schema.table_io_waits_summary_by_index_usage表,我们可查看哪些索引被使用、哪些索引未被使用(可删除无用索引):
-- 查看索引使用次数(按使用次数升序,找出未使用的索引)
select
table_schema,
table_name,
index_name,
count_star as use_count -- 使用次数
from performance_schema.table_io_waits_summary_by_index_usage
where table_schema = 'test_db' -- 目标数据库
order by use_count asc;
提示:Performance Schema的统计信息在MySQL重启后清零,因此“未使用索引”可能是“刚上线还未触发”,我们建议结合业务周期(如:一周)综合判断。
三、sys schema:Performance Schema的简化接口,降低性能分析的门槛
Performance Schema的表结构复杂(超过100张表),查询语句繁琐。MySQL 5.7+引入了sys schema:基于Performance Schema和Information Schema的视图集合,为我们提供了简洁的查询接口,让性能分析更高效。
1、常用sys视图示例
场景1:查看未使用的索引(无用索引)
-- 查看test_db数据库中未使用的索引
select * from sys.schema_unused_indexes where table_schema = 'test_db';
场景2:查看全表扫描的SQL
-- 查看最近执行过全表扫描的SQL
select * from sys.statements_with_full_table_scans order by exec_count desc;
场景3:查看锁等待情况
-- 查看当前的锁等待
select * from sys.innodb_lock_waits;
该视图会显示等待锁的事务ID、持有锁的事务ID、等待的锁类型、涉及的表和行等信息,帮助我们快速定位谁在锁表、锁了多久。
场景4:查看IO消耗最高的表
-- 按IO消耗排序,找出最耗IO的表
select * from sys.io_global_by_file_by_bytes order by total_bytes desc limit 10;
如果某张表的total_bytes(总IO字节数)远远高于其他表,可能是该表频繁被全表扫描,我们需优先优化它。
四、定位之后:验证与优化闭环
在通过上述三层手段(慢查询日志、Performance Schema、sys schema)精准定位慢SQL后,我们下一步是分析原因并实施优化,而这一过程必须以可验证的数据为基础:
-
务必使用
EXPLAIN或MySQL 8.0+的EXPLAIN ANALYZE验证执行计划。EXPLAIN提供的是优化器预估的执行路径,而EXPLAIN ANALYZE会实际执行语句(注意:对写操作需谨慎),返回真实的扫描行数、循环次数、内存使用、是否使用临时表或文件排序等关键信息,能有效判断索引是否生效、是否存在隐式类型转换等问题。 -
建立“定位 → 分析 → 优化 → 验证”的闭环机制:
-
定位:通过慢日志或实时监控发现可疑SQL; -
分析:结合 Rows_examined、锁等待、IO消耗、执行计划等判断瓶颈; -
优化:添加合适索引、重写SQL、调整表结构或配置参数; -
验证:对比优化前后 Query_time、Rows_examined、CPU/IO负载等指标,确保改进有效且无副作用。
-
只有形成这样的闭环,才能真正将“慢SQL治理”从被动救火转变为主动防控,持续保障高并发、大数据量场景下的系统稳定性与用户体验。
版权保护: 本文由 绿茶加糖-郭保升 原创,转载请保留链接: https://www.guobaosheng.com/shujuku/440.html
