MySQL查询很慢,怎样找出拖慢业务的SQL
网站变慢时,先找出哪些 SQL 花了时间,再决定改查询、加索引还是调整资源。本篇针对 Linux 自管 MySQL 8.0/8.4,用一个短时采集窗口记录慢查询,并在结束后恢复原参数。MariaDB 的参数差异、托管数据库和已有集中日志方案不直接套用。
保存原设置,再决定是否需要调整
使用有系统变量管理权限的 MySQL 管理账号执行下面的查询,保存完整结果。采集期间由同一位管理员调整这些参数。
SELECT @@hostname, VERSION();
SELECT @@GLOBAL.slow_query_log,
@@GLOBAL.log_output,
@@GLOBAL.slow_query_log_file,
@@GLOBAL.long_query_time,
@@GLOBAL.min_examined_row_limit,
@@GLOBAL.log_queries_not_using_indexes,
@@GLOBAL.log_timestamps;
下文使用文件日志,因此 log_output 必须包含 FILE,并且日志所在磁盘有余量。如果当前只有 TABLE 或 NONE,先按现有采集方案处理;直接改变 log_output 还会影响通用查询日志,不在本篇顺手切换。日志路径以查询结果为准。
若日志已经启用、阈值也能覆盖当前故障,直接读取现有记录。需要临时采集时,可先用1秒阈值观察5至10分钟;这是起始示例,按业务响应目标调整,不长期开到0秒记录全部SQL。
临时打开日志,并验证当前连接的阈值
保留这个 MySQL 管理会话。下面保存三个会被修改的原值,再启用采集;原值同时保存在你前面记录的查询结果里,以防连接中断。
SET @bl_old_slow_log = @@GLOBAL.slow_query_log;
SET @bl_old_long_time = @@GLOBAL.long_query_time;
SET @bl_old_min_rows = @@GLOBAL.min_examined_row_limit;
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL min_examined_row_limit = 0;
全局阈值影响之后建立的连接,已有连接池可能仍使用旧值。不要为了让日志生效就重启数据库。可以通过程序的受控连接更新,让新连接继承新值,并确认实际会话的 @@SESSION.long_query_time。
先在当前管理会话单独验证记录链路。这条测试只等待1.2秒,不读取业务表:
SET SESSION long_query_time = 1;
SET SESSION min_examined_row_limit = 0;
SELECT SLEEP(1.2);
随后在 Linux 终端查看日志末尾,把路径换成第一步的真实路径:
sudo tail -n 80 -- /实际路径/mysql-slow.log
应找到对应时间的 SELECT SLEEP(1.2)。没有记录时,依次检查日志是否启用、输出是否包含FILE、当前会话阈值、最小扫描行数、日志路径和写入错误。测试SQL出现,只证明采集链路可用;还需要采到真实业务请求。
先看累计影响,再看一条SQL为什么慢
| 字段 | 怎么读 |
|---|---|
Query_time |
该次语句耗时。应与业务报错时间、请求耗时一起看。 |
Lock_time |
日志记录的锁获取耗时。不能把它当成所有锁等待的完整清单。 |
Rows_examined / Rows_sent |
扫描很多行却只返回少量结果时,值得检查条件、索引和分页方式。 |
| 相似SQL的次数 | 单次不算最慢、但执行非常频繁的查询,也可能消耗大量资源。 |
系统有 mysqldumpslow 时,可分别查看耗时靠前和次数靠前的查询类型:
sudo mysqldumpslow -s t -t 10 /实际路径/mysql-slow.log
sudo mysqldumpslow -s c -t 10 /实际路径/mysql-slow.log
输出会合并结构相近的SQL,默认把部分数字和字符串替换成占位符;它不是完整脱敏工具。选出影响最大的几类,记录实际 SQL、业务接口、时间和出现次数,再用 EXPLAIN 检查执行计划。EXPLAIN ANALYZE 会实际执行语句,不要把昂贵的查询直接拿到高峰期重跑。
先在测试数据或可控时段验证索引、条件和分页调整,每次只改一个主要因素。优化后比较相同请求的耗时、扫描行数和错误情况;不要见到 Rows_examined 大就给每个字段都建索引。
结束采集,恢复原设置
确认没有其他管理员修改这些参数后,回到仍保留原值的管理会话执行:
SET GLOBAL long_query_time = @bl_old_long_time;
SET GLOBAL min_examined_row_limit = @bl_old_min_rows;
SET GLOBAL slow_query_log = @bl_old_slow_log;
SELECT @@GLOBAL.slow_query_log,
@@GLOBAL.long_query_time,
@@GLOBAL.min_examined_row_limit;
如果原会话已断开,@bl_old_... 就不存在了,应从第一步记录中取回具体值,不能在新会话原样执行恢复代码。全局值恢复后,采集期间建立的连接也可能保留临时会话阈值,按原程序的连接管理方式恢复。核对恢复结果后执行 quit; 退出本次管理连接;前面设置的会话级阈值不会随 SET GLOBAL 自动恢复。本文没有写入持久配置。
保留必要的故障时间段和优化对照结果,日志按现有轮转方案管理。SQL中可能含客户资料和令牌,对外反馈前先脱敏;磁盘空间不足时可结合 Linux磁盘占用排查 确认增长来源。