-
三个为什么,让我们一步到位搞清排查慢SQL常见方法
- 网站名称:三个为什么,让我们一步到位搞清排查慢SQL常见方法
- 网站分类:技术文章
- 收录时间:2025-10-02 16:17
- 网站地址:
“三个为什么,让我们一步到位搞清排查慢SQL常见方法” 网站介绍
不管是MySQL、Oracle还是我们还没有用过的其他什么数据库,慢SQL都是系统性能瓶颈的主要诱因。一条未优化的SQL语句就可能让数据库CPU跑满、连接池耗尽,甚至引发整个服务雪崩。下面,我们以常用的MySQL 8.0为例,先搞明白慢SQL的原理,再一步步仔细拆解怎么定位慢SQL?怎么看懂它的执行计划?如果我们把MySQL 8.0慢SQL排查方法搞清楚了,我们不仅能彻底解决眼下的MySQL 8.0慢SQL问题,以后我们碰到其他数据库的类似情况,也能照着这个思路搞定慢SQL。
一、为什么会出现慢SQL?
我们先搞懂MySQL执行SQL的底层逻辑,知道SQL是怎么跑起来的?
1、MySQL执行SQL的流程
MySQL架构主要包括Server层、存储引擎层等两层,一条SQL从发出到返回,大概需经历8步:
(1)连接建立:我们客户端通过TCP连接MySQL服务器,服务器通过连接池管理连接(默认端口3306);
(2)查询缓存(MySQL 8.0已移除):检查SQL是否在缓存中,若命中则直接返回结果;
(3)语法解析:词法分析(拆分SQL关键词,如:select、from、where)→语法分析(判断SQL语法是否正确,生成“语法树”);
(4)语义分析:检查表、字段是否存在,用户是否有操作权限;
(5)查询优化:MySQL优化器(基于成本和规则)生成最优执行计划(如:选择全表扫描还是索引扫描);
(6)执行计划生成:将优化后的执行计划传递给执行器;
(7)执行器执行:调用存储引擎接口(如:InnoDB),执行查询并获取数据;
(8)结果返回:将数据封装后返回给我们客户端,同时释放连接资源。
这8步中,某一步或几步执行耗时过长,主要是查询优化(生成差的执行计划)和执行器执行(全表扫描、索引失效等)这两步,就会出现慢SQL。
2、如何通过资源监控快速定位慢SQL问题?
MySQL性能依赖“CPU、内存、磁盘IO、网络”这些资源,慢SQL出现必然伴随至少一种资源瓶颈,具体表现如表:
资源类型 | 瓶颈表现 | 典型场景 |
CPU | CPU 使用率持续>90% | 复杂聚合查询(group by/order by无索引)、大量计算函数(count(*)/sum()) |
内存 | 内存使用率高,频繁发生页置换 | InnoDB 缓存池(buffer pool)设置过小,大量数据需从磁盘加载 |
磁盘IO | 磁盘IO利用率>80%,读写延迟高 | 全表扫描、无序写入(如:未按主键插入)、频繁刷盘 |
网络 | 网络带宽占满,传输延迟高 | select * 查询返回大量数据、大字段(text/blob)传输 |
3、慢SQL判定标准
慢由我们业务场景决定,没有固定阈值,只是相对来说:
- 高并发场景(如:秒杀、首页接口):响应时间需<500ms,超过则视为慢SQL;
- 普通业务场景(如:用户中心、订单详情):响应时间需<2s,超过则视为慢SQL;
- 离线场景(如:数据报表、批量导出):响应时间可放宽至5-10s。
通用做法:我们通过long_query_time参数设置慢查询阈值(默认10s,我们建议设为2s),超过这个时间的SQL会被记录到慢查询日志中,作为备选优化对象。
二、怎么定位慢SQL?
1、慢查询日志:初步定位谁慢了
慢查询日志是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';
执行结果示例(默认未开启状态):
Variable_name | Value |
slow_query_log | OFF |
slow_query_log_file | /var/lib/mysql/localhost-slow.log |
long_query_time | 10.000000 |
log_output | FILE |
log_queries_not_using_indexes | OFF |
第二步:临时开启(应急排查)
若无需重启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';
第三步:永久开启(实际业务场景)
临时配置重启后失效,实际业务场景我们需修改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
# 记录慢查询的最小锁定时间(单位:秒,可选)
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以上),我们手动打开查找,效率极低,这时需借助工具进行筛选和聚合分析。
工具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
mysqldumpslow -u app_user /var/lib/mysql/mysql-slow.log
# 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
报告解读:
pt-query-digest生成的报告分为摘要和详细分析两部分,摘要部分我们要关注:
- Total:总执行时间、总执行次数;
- Avg:平均执行时间;
- 95%:95%的SQL执行时间(比平均时间更能反映真实性能);
- RowsExam:平均扫描行数;
- Query:SQL模板(相似SQL聚合后的模板)。
2、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
(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在解析、优化、执行等阶段的耗时:
select
digest_text as sql_text, -- SQL模板
count_star as execute_count, -- 执行次数
sum_timer_parse as parse_time, -- 解析耗时(皮秒,需转换为秒)
sum_timer_optimize as optimize_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;
3、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字节数)远高于其他表,可能是该表频繁被全表扫描,我们需优先优化它。
三、怎么看懂执行计划?
抓到慢SQL后,我们还不能盲目优化(比如:随便加索引),我们必须先搞懂MySQL是怎么执行它的?这就需要执行计划。执行计划是MySQL优化器生成的执行路线图,包含扫描方式、索引使用、数据过滤逻辑等信息,我们诊断慢SQL时经常要用到。
1、生成执行计划:EXPLAIN与EXPLAIN ANALYZE
MySQL提供两种生成执行计划的命令:EXPLAIN(预估执行计划)和EXPLAIN ANALYZE(MySQL 8.0.18+新增,实际执行并返回真实数据)。
(1)EXPLAIN:预估执行计划
针对慢SQLselect * from order_info where user_id = 567 and create_time between '2024-01-01' and '2024-06-01';,我们在其前面加EXPLAIN即可生成预估执行计划:
EXPLAIN select * from order_info where user_id = 567 and create_time between '2024-01-01' and '2024-06-01';
执行后返回一张表格,包含12个字段,我们新手需重点掌握这8个字段:id、select_type、table、type、possible_keys、key、rows、Extra。
(2)EXPLAIN ANALYZE:真实执行计划
EXPLAIN仅提供预估信息,与实际执行情况可能存在偏差。MySQL 8.0.18+引入的EXPLAIN ANALYZE会实际执行SQL语句,并输出执行过程中各操作符的真实性能数据(如:耗时、实际行数),与优化器预估数据对比,帮助我们精准定位执行计划偏差。
EXPLAIN ANALYZE select * from order_info where user_id = 567 and create_time between '2024-01-01' and '2024-06-01';
执行结果示例:
-> Filter: ((order_info.user_id = 567) and (order_info.create_time between '2024-01-01 00:00:00' and '2024-06-01 23:59:59')) (cost=1200.00 rows=150) (actual time=0.02..3.80 rows=150 loops=1)
-> Table scan on order_info (cost=1200.00 rows=120000) (actual time=0.01..2.50 rows=120000 loops=1)
通过actual time(真实时间)和rows(真实扫描行数),我们可直接判断SQL的瓶颈。
2、执行计划主要字段解读
以“未建索引”和“建索引后”的执行计划对比,我们逐字段解读:
(1)id:查询序号
表示查询的层级(单表查询为1;多表联查或子查询时,序号越大优先级越高,先执行)。我们看示例:
- 单表查询:id=1;
- 子查询:select * from user where order_count > (select count(*) from order_info where user_id = user.id),子查询id=2,主查询id=1,先执行子查询。
(2)select_type:查询类型
表示查询的复杂程度(如:简单查询、子查询、联合查询等),常见类型如下:
- SIMPLE:简单查询(无子查询、无联合查询);
- SUBQUERY:子查询(select子句中的子查询);
- DERIVED:派生表(from子句中的子查询,会生成临时表);
- UNION:联合查询(union后的查询);
- UNION RESULT:联合查询的结果集。
优化提示:DERIVED和UNION可能产生临时表,若数据量大需优化(如:拆分子查询)。
(3)table:查询的表
表示当前行对应的表名(若为派生表,会显示derivedN,N为子查询的id)。
(4)type:连接类型(核心指标!)
表示MySQL访问表的方式(即:如何获取数据)。
- 从优到劣排序:
- system:表中只有1条记录(如:系统表),效率最高;
- const:通过主键或唯一索引查询,匹配1条记录(如:where id=1);
- eq_ref:多表联查时,通过主键或唯一索引匹配,每行只返回1条记录(如:user join order on user.id=order.user_id);
- ref:通过非唯一索引查询,可能返回多条记录(如:where name='张三');
- range:索引范围查询(如:between、>、<、in);
- index:扫描整个索引(比全表扫描快,因为索引文件比数据文件小);
- ALL:全表扫描(效率最低,必须优化)。
- 优化标准:在我们的实际业务场景中,type应至少达到range级别,最好是ref或const;若为ALL,我们需立即优化。
- 示例对比:
- 未建索引:type=ALL(全表扫描);
- 建索引后:type=range(范围查询)。
(5)possible_keys:可能使用的索引
MySQL优化器认为“可能适用”的索引列表(但不一定实际使用)。我们看示例:若表有idx_user_id和idx_create_time两个索引,possible_keys可能显示idx_user_id,idx_create_time。
(6)key:实际使用的索引
MySQL实际执行时使用的索引(若为NULL,表示未使用任何索引)。
- 优化提示:若possible_keys有值,但key为NULL,说明索引失效或优化器选择了更差的执行计划(我们需分析原因)。
- 示例对比:
- 未建索引:key=NULL;
- 建索引后:key=idx_user_create(联合索引)。
(7)key_len:使用的索引长度
表示MySQL使用的索引字节数(可判断索引使用的完整性)。
- 计算规则:
- 字符类型:varchar(20) utf8mb4编码,key_len=20*4 + 2=82(4字节/字符,2字节存储长度);
- 数值类型:int占4字节,bigint占8字节;
- 可为空:需额外加1字节。
- 优化提示:若key_len小于索引总长度,说明索引未被完全使用(如:联合索引只用到了前几个字段)。
(8)rows:预估扫描行数
MySQL优化器预估需要扫描的行数(值越小越好)。我们看示例对比:
- 未建索引:rows=120000(预估扫描12万行);
- 建索引后:rows=150(预估扫描150行)。
(9)Extra:额外信息(重要!)
表示MySQL执行过程中的额外操作(很多时候是慢查询的直接原因)。
- 常见类型:
- Using where:使用where条件过滤数据(但未使用索引,需全表扫描后过滤);
- Using index:使用“覆盖索引”(无需回表,直接从索引获取数据,效率高);
- Using filesort:无法通过索引排序,需在内存/磁盘中排序(耗时,需优化);
- Using temporary:使用临时表存储中间结果(如:group by/distinct,耗时,需优化);
- Using join buffer:多表联查时使用连接缓冲区(说明未使用索引,需优化);
- Using index condition:使用“索引下推”(MySQL 5.6+新增,在存储引擎层过滤数据,效率高)。
- 优化提示:Using filesort和Using temporary是两大“性能瓶颈”,需优先优化。
3、执行计划里常见的异常情况
结合我们的实际业务场景,我们来分析以下6种典型的执行计划异常情况及原因:
(1)type=ALL(全表扫描)
- 现象:type=ALL,key=NULL,rows很大。
- 原因:
- 表太小(数据量<1000行),MySQL认为全表扫描更快;
- 未建索引;
- 索引失效(如:左模糊、函数操作字段)。
- 优化:我们建合适的索引,避免索引失效。
(2)possible_keys有值,但key=NULL(索引未使用)
- 现象:possible_keys=idx_user_id,key=NULL。
- 原因:
- 索引选择性差(如:“性别”字段,只有“男/女”,索引过滤效果差);
- 数据量太小,MySQL认为全表扫描更快;
- 统计信息过时,优化器判断错误。
- 优化:
- 我们删除选择性差的索引;
- 更新统计信息(analyze table order_info;);
- 我们用force index强制使用索引(谨慎使用)。
(3)Extra=Using filesort(文件排序)
- 现象:Extra=Using filesort,执行时间长。
- 示例SQL:select * from user where age>30 order by name;
- 原因:排序字段name未加入索引,MySQL需先全表扫描过滤age>30,再对结果排序。
- 优化:我们建联合索引(age, name),让排序通过索引完成。
(4)Extra=Using temporary(临时表)
- 现象:Extra=Using temporary,执行时间长。
- 示例SQL:select count(*) from user group by age;
- 原因:group by字段age未建索引,MySQL需创建临时表存储分组结果。
- 优化:我们建索引(age),避免临时表。
(5)key_len小于索引长度(索引未完全使用)
- 现象:联合索引(a,b,c),key_len只对应a的长度。
- 示例SQL:select * from user where b=1 and c>10;
- 原因:违反“最左前缀原则”,联合索引需从左到右使用,跳过a直接查b会导致索引失效。
- 优化:我们调整查询条件,或建索引(b,c)。
(6)type=ref但rows很大(索引选择性差)
- 现象:type=ref,但rows=10000(扫描行数多)。
- 原因:索引字段选择性差(如:“性别”、“状态”等低基数字段)。
- 优化:我们删除该索引,或与其他字段组合成联合索引(提升选择性)。
更多相关网站
- SQL 语句大全_sql语句大全经典教程
- 查询表修改记录_查询表修改记录怎么删除
- 让这几条SQL来应对你的测试工作日常
- SQL语句大全,所有的SQL都在这里_sql语句大全基础
- MySQL 从入门到精通(六) SQL性能分析及索引使用规则
- SQL子查询入门:嵌套查询其实没那么难
- SQL高级:SQL 的常见问题-慢查询排查
- SQL慢查询优化实战:从诊断到架构升级的全链路解决方案
- 啥样的SQL算复杂?看这个电商订单多维度统计查询就懂了
- 如何使用子查询优化SQL表达式?_sql子查询怎么实现
- 一文讲懂SQL子查询_sql语言中,子查询是什么
- 7大类36小类SQL查询常见易错点,有谁踩坑了?
- 常见SQL子查询语法示例,覆盖15种场景实操!
- SQL BETWEEN:查询范围内的数据,就这么简单!
- 10类SQL查询语句语法结构模板,覆盖90%SQL查询语句应用场景
- sql年月查询多种方式_sql查询年份语句
- 最近发表
- 标签列表
-
- mydisktest_v298 (35)
- sql 日期比较 (33)
- document.appendchild (35)
- 头像打包下载 (35)
- 梦幻诛仙表情包 (36)
- java面试宝典2019pdf (26)
- disk++ (30)
- 加密与解密第四版pdf (29)
- iteye (26)
- centos7.4下载 (32)
- intouch2014r2sp1永久授权 (33)
- jdk1.8.0_191下载 (27)
- axure9注册码 (30)
- 兔兔工程量计算软件下载 (27)
- ccproxy破解版 (31)
- aida64模板 (28)
- engine=innodb (33)
- shiro jwt (28)
- segoe ui是什么字体 (27)
- head first java电子版 (32)
- clickhouse中文文档 (28)
- jdk-8u181-linux-x64.tar.gz (32)
- 计算机网络自顶向下pdf (34)
- -dfile.encoding=utf-8 (33)
- jdk1.9下载 (32)