ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

【MySQL】慢查询日志 Slow Log:定位低效 SQL

【MySQL】慢查询日志 Slow Log:定位低效 SQL 1. 慢查询日志概述慢查询日志Slow Query Log是 MySQL 提供的一种用于记录执行时间超过指定阈值的 SQL 语句的日志机制。它就像数据库的“行车记录仪”当一条 SQL 的响应时间超过你设定的“安全线”时MySQL 会把这整条语句连同执行耗时、扫描行数、返回行数、锁等待时间等关键信息记录下来供 DBA 和开发人员事后分析。在实际业务中慢查询日志是定位性能问题的第一入口。当用户反馈“页面加载很慢”“接口超时”“数据库 CPU 飙高”时最直接的做法通常不是盲目猜测而是打开慢查询日志找出最近一段时间内最耗时的 SQL再针对性地做索引优化、SQL 改写或者架构调整。需要特别强调的是慢查询日志记录的是“慢”的 SQL而不是“错”的 SQL。也就是说一条 SQL 即使语法完全正确、返回结果正确只要执行时间超过了阈值就会被记录。慢查询并不一定意味着需要立刻修复有些后台统计任务、数据迁移任务天然耗时较长但这不等于它们是不合理的。分析慢查询日志的目标是区分“合理的慢”与“不合理的慢”把那些本可以快但因缺少索引、写法低效而变慢的 SQL 找出来。2. 为什么需要慢查询日志在讨论如何分析慢查询日志之前先理解它为什么是数据库调优中不可或缺的工具。2.1 快速定位性能瓶颈一个业务系统的性能通常由前端、网络、应用层、缓存层和数据库层共同决定。当系统变慢时如果没有日志支撑排查过程往往像大海捞针。慢查询日志提供了客观证据哪条 SQL 慢、慢了多少秒、扫描了多少行、是否加了锁。通过这些信息可以快速把问题收敛到数据库层甚至某一条具体语句上。2.2 为索引设计提供依据很多慢查询的根因是“全表扫描”和“索引失效”。慢查询日志中记录了Rows_examined扫描行数和Rows_sent返回行数两个核心字段。当扫描行数远大于返回行数时通常意味着索引设计不合理。比如一条 SQL 只需要返回 10 行数据却扫描了 100 万行这几乎可以断定存在索引缺失或索引选择不当的问题。2.3 发现隐藏的性能隐患某些 SQL 在数据量小的时候执行很快随着数据增长逐渐变慢。慢查询日志可以帮助你在用户感知到明显卡顿之前提前发现这些“温水煮青蛙”式的问题。定期分析慢查询日志观察某条 SQL 的执行时间趋势是数据库巡检的重要环节。2.4 评估优化效果当你为某条慢 SQL 添加了索引、改写了查询逻辑之后如何验证优化是否有效可以对比优化前后这条 SQL 的执行时间。慢查询日志中记录的时间戳和执行耗时为这种前后对比提供了可靠的数据基础。3. 慢查询日志的开启与关闭默认情况下MySQL 的慢查询日志是关闭的因为开启它会产生额外的磁盘写入开销虽然通常不大但在写入非常密集的场景下仍然需要权衡。以下是开启慢查询日志的完整步骤。3.1 查看当前慢查询日志状态首先通过以下命令查看慢查询日志是否开启、日志文件位置以及相关阈值SHOW VARIABLES LIKE slow_query_log%; SHOW VARIABLES LIKE long_query_time;执行结果示例-------------------------------------------------------- | Variable_name | Value | -------------------------------------------------------- | slow_query_log | OFF | | slow_query_log_file | /var/lib/mysql/mysql-slow.log | -------------------------------------------------------- ---------------------------- | Variable_name | Value | ---------------------------- | long_query_time | 10.000000 | ----------------------------从结果可知当前慢查询日志处于关闭状态日志文件路径为/var/lib/mysql/mysql-slow.log慢查询时间阈值为 10 秒。3.2 临时开启会话级重启失效如果只是想临时排查问题不想修改配置文件可以直接通过命令行动态开启SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 2;这种方式的优点是即时生效缺点是 MySQL 重启后配置会丢失。值得注意的是long_query_time修改后对已经建立的连接不一定立即生效新建连接才会使用新的阈值。3.3 永久开启配置文件方式生产环境通常将慢查询日志配置写入 MySQL 配置文件确保重启后仍然生效。编辑my.cnf或my.ini[mysqld] slow_query_log 1 slow_query_log_file /var/lib/mysql/mysql-slow.log long_query_time 2 log_queries_not_using_indexes 0 log_output FILE保存后重启 MySQL 服务使配置生效。其中各参数含义将在下一节详细解释。3.4 关闭慢查询日志当排查工作结束可以在业务低峰期关闭慢查询日志以减少磁盘开销SET GLOBAL slow_query_log OFF;如果写在配置文件中将slow_query_log 1改为slow_query_log 0并重启即可。4. 慢查询日志核心参数详解慢查询日志的行为由多个系统变量共同控制理解这些参数是正确使用慢查询日志的基础。下面逐个说明。4.1 slow_query_log控制慢查询日志的总开关取值为ON或OFF也可以写作 1 和 0。该参数可以全局动态修改。SET GLOBAL slow_query_log ON;4.2 slow_query_log_file指定慢查询日志文件的存放路径和文件名。如果未显式设置MySQL 会根据datadir和主机名自动生成默认文件名通常形如hostname-slow.log。该参数修改后需要重启实例或重新开启日志开关才能改变实际写入位置属于静态参数不能通过SET GLOBAL直接生效。建议将慢查询日志文件放在独立的挂载盘上避免与数据文件、二进制日志争抢磁盘 I/O 带宽。4.3 long_query_time这是最核心的阈值参数单位是秒默认值为 10。当一条 SQL 的执行时间严格大于该值时才会被记录到慢查询日志。注意是“大于”而非“大于等于”也就是执行时间恰好等于 10 秒的语句不会被记录。该参数支持小数例如设置为 0.5 表示超过 0.5 秒的查询就会被记录SET GLOBAL long_query_time 0.5;需要留意的是在 MySQL 早期版本中long_query_time的最小精度受版本限制部分版本最小可设置为 1 秒或 0.1 秒。从 MySQL 5.1.21 开始支持微秒级记录但阈值本身通常以秒为单位可以设置到小数点后多位。在 MySQL 8.0 之前还有一个min_examined_row_limit参数配合使用表示执行时间超过阈值且扫描行数达到该值的 SQL 才会被记录从而过滤掉一些虽然超时但扫描行数很少、可能是被锁阻塞的语句。4.4 log_queries_not_using_indexes该参数用于控制是否记录“没有使用索引”的查询。当设置为 1 时任何未使用索引进行检索的查询即使执行时间没有超过long_query_time也会被记录到慢查询日志中。SET GLOBAL log_queries_not_using_indexes ON;开启这个选项后慢查询日志中会出现大量“很快但没有走索引”的 SQL方便检查全表扫描情况。代价是日志量会显著增大可能包含很多低风险的小表全表扫描因此需要配合log_throttle_queries_not_using_indexes参数限制每分钟最多记录多少条此类日志避免日志爆炸。4.5 log_throttle_queries_not_using_indexes配合上一条参数使用设定每分钟最多记录多少条“未使用索引”的查询。默认值为 0表示不限制。当设置为 0 时每一条未使用索引的 SQL 都会记录当设置为一个正整数时MySQL 会在每分钟内限制此类日志的数量超出的部分仅计数不落盘。SET GLOBAL log_throttle_queries_not_using_indexes 10;该参数在高流量场景下非常实用既保留排查全表扫描的能力又避免慢查询日志被海量低风险 SQL 淹没。4.6 log_output指定慢查询日志的输出目标可选值包括FILE、TABLE和NONE也可以组合使用例如FILE,TABLE表示同时写入文件和表。当设置为TABLE时慢查询会记录到mysql.slow_log系统表中方便用 SQL 直接查询分析SET GLOBAL log_output TABLE; SET GLOBAL slow_query_log ON; SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 20;写入表的好处是可以利用 SQL 的查询、排序、聚合能力做分析坏处是慢日志表本身也会占用存储空间如果日志量巨大mysql.slow_log表会迅速膨胀甚至反过来拖慢实例。因此生产环境更常见的是输出到文件配合外部分析工具处理。4.7 log_slow_admin_statements默认情况下像OPTIMIZE TABLE、ANALYZE TABLE、ALTER TABLE这类管理语句即使执行很慢也不会被记录到慢查询日志。如果需要跟踪这些管理操作可以开启SET GLOBAL log_slow_admin_statements ON;4.8 log_slow_replica_statementsMySQL 8.0.26 之前为 log_slow_slave_statements在从库上执行的 SQL 默认也会被记录到从库的慢查询日志吗答案取决于版本和参数。在 MySQL 5.7 中从库 SQL 线程执行的语句默认不写入慢查询日志如需写入需开启log_slow_slave_statementsMySQL 8.0.26 之后该参数改名为log_slow_replica_statements。对于主从复制延迟排查开启该参数很有帮助因为从库上的回放慢往往能反映出主库写入压力或从库硬件瓶颈。4.9 log_timestamps控制慢查询日志和错误日志中时间戳的时区可选值为UTC和SYSTEM默认是UTC。如果你希望日志里的时间与系统本地时间一致可以设置为SET GLOBAL log_timestamps SYSTEM;该参数影响的是日志里显示的时间不会影响数据本身存储的时间。4.10 long_query_time 与 DDL、DML 的关系需要澄清一个常见误解慢查询日志不只记录SELECT查询也会记录执行时间超过阈值的INSERT、UPDATE、DELETE以及部分 DDL 语句。判断标准只有一个语句执行耗时是否超过long_query_time。因此当你看到慢查询日志里大量出现写入语句时不要惊讶它们同样值得关注尤其是那些因表锁竞争、缺少合适索引而导致更新变慢的语句。5. 慢查询日志文件格式深度解析理解慢查询日志的文件格式是后续手工分析或编写解析脚本的前提。下面通过一段真实日志逐行拆解。5.1 一条完整的慢查询记录假设慢查询日志文件中有如下内容# Time: 2026-08-30T15:20:03.812345Z # UserHost: app_user[app_user] [10.0.12.35] Id: 102938 # Query_time: 8.523456 Lock_time: 0.000123 Rows_sent: 15 Rows_examined: 980000 SET timestamp1765020003; SELECT user_name, email FROM users WHERE create_time 2026-08-01 ORDER BY id DESC LIMIT 15;下面逐行解释每个字段的含义。5.2 Time 字段# Time: 2026-08-30T15:20:03.812345Z表示该语句的执行开始时间采用 UTC 或系统时区由log_timestamps决定。末尾的Z是 UTC 的标志。5.3 UserHost 字段# UserHost: app_user[app_user] [10.0.12.35] Id: 102938记录了执行该语句的数据库账号、来源主机 IP 以及连接线程 ID。这个信息在定位“是哪个应用服务器、哪个业务账号发出的慢查询”时非常关键。5.4 关键性能指标字段下面这一行是慢查询日志中最有价值的信息# Query_time: 8.523456 Lock_time: 0.000123 Rows_sent: 15 Rows_examined: 980000Query_time语句总执行时间单位是秒精确到微秒。这是判断语句是否“慢”的直接依据。Lock_time等待锁的时间单位是秒。如果该值很大说明语句大部分时间花在等待表锁或元数据锁上而不是真正执行查询。Rows_sent返回给客户端的行数。Rows_examined执行过程中扫描的行数。该值越大通常意味着查询越可能进行了全表扫描或低效索引扫描。对于上面的例子Rows_sent15而Rows_examined980000即为了返回 15 行数据扫描了 98 万行这几乎可以断定create_time字段上没有合适的索引或者查询优化器没有使用它。5.5 SET timestampSET timestamp1765020003;用于记录语句执行时的 Unix 时间戳某些分析工具会基于它做时间维度聚合。它不是业务 SQL 的一部分在回放时需要忽略。5.6 SQL 语句本体最后一行或多行是真正的 SQL 语句。对于多行 SQL、存储过程调用等MySQL 会完整记录其文本。对于包含二进制数据或超长文本的语句日志中可能显示截断后的内容或者...省略号。5.7 InnoDB 相关扩展字段在 InnoDB 引擎下部分版本和场景中还会记录额外信息例如Rows_affected、Bytes_sent、Thread_id、Errno等。以 MySQL 8.0 为例更多指标会被写入具体取决于版本和存储引擎。对于写入语句可能看到类似# Query_time: 3.214567 Lock_time: 0.000421 Rows_sent: 0 Rows_examined: 1 # Rows_affected: 1 Bytes_sent: 52理解这些字段后即使不借助任何工具也能手工判断一条慢查询的核心问题。6. 使用 mysqldumpslow 分析慢查询日志MySQL 官方自带了一个轻量级的慢查询日志分析工具mysqldumpslow它可以对慢查询日志进行统计汇总帮助快速了解日志中高频出现的慢查询模式。它不修改日志文件只做读取分析。6.1 基本用法mysqldumpslow是 Perl 脚本通常随 MySQL 安装包一同提供。最基本的使用方式是直接传入日志文件mysqldumpslow /var/lib/mysql/mysql-slow.log输出示例Count: 120 Time3.52s (422s) Lock0.00s (0s) Rows_sent15.0 (1800), root[root]localhost SELECT user_name, email FROM users WHERE create_time S ORDER BY id DESC LIMIT N解释一下输出格式Count该 SQL 模式出现的次数。Time该模式的平均执行时间和总执行时间。Lock平均锁等待时间和总锁等待时间。Rows_sent平均返回行数和总返回行数。后面的root[root]localhost表示执行用户和来源主机。6.2 常用参数参数说明-s t按总执行时间排序默认还有at平均时间、l锁时间、al平均锁时间、c次数、r返回行数等-r倒序排列与-s配合使用-t N只显示前 N 条结果-a不把数字和字符串抽象为 N 和 S显示完整语句-n N抽象数字时显示的最少位数-g pattern使用正则表达式过滤结果只显示匹配的 SQL-l不合并锁等待时间中的数字和查询时间6.3 实战示例按平均执行时间倒序显示前 10 条最慢的 SQL 模式mysqldumpslow -s at -r -t 10 /var/lib/mysql/mysql-slow.log只统计包含特定关键字的慢查询例如所有涉及order表mysqldumpslow -g order /var/lib/mysql/mysql-slow.log显示完整 SQL 文本而不做数字/字符串抽象mysqldumpslow -a -t 5 /var/lib/mysql/mysql-slow.log6.4 局限性与适用场景mysqldumpslow的优点是零依赖、开箱即用适合快速扫一眼慢查询日志里的大致情况。但它有明显局限统计粒度较粗无法提供百分位耗时、无法做复杂的多维分析也无法给出优化建议。对于较大的生产日志通常需要更进一步使用pt-query-digest这类专业工具。7. 使用 pt-query-digest 做进阶分析pt-query-digest是 Percona Toolkit 中最常被用来分析慢查询日志的工具功能远强于mysqldumpslow。它不仅能输出统计报告还能把 SQL“指纹化”归类、计算响应时间分布、生成可视化摘要甚至可以将分析结果写入数据库表。7.1 安装 Percona Toolkit在常见的 Linux 发行版上可以通过包管理器安装。以 CentOS / RHEL 为例先配置 Percona 仓库然后安装yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm yum install percona-toolkit在 Ubuntu / Debian 上apt update apt install percona-toolkit7.2 基本用法直接对慢查询日志文件运行分析pt-query-digest /var/lib/mysql/mysql-slow.log slow_report.txt报告会分为多个部分包括总体统计、按响应时间排序的查询汇总、每种查询的详细分析、表统计信息和索引建议等。7.3 报告结构解读pt-query-digest的报告中每个 Query Class 会显示类似下面的信息# Profile # Rank Query ID Response time Calls R/Call V/M Item # # 1 0x3F2A1B9C8E 422.0000 50.0% 120 3.5167 0.85 SELECT users # 2 0xB8D7E4A2C1 187.2000 22.2% 32 5.8500 0.12 UPDATE orders各列含义如下Rank按响应时间占比的排名。Query ID该查询模式的指纹标识相同 SQL 模板拥有相同的 Query ID。Response time该模式的累计响应时间及其占总时间的百分比。Calls执行次数。R/Call每次调用的平均响应时间。V/M方差均值比用于衡量该语句响应时间的稳定性值越大表示越不稳定。7.4 常用参数参数说明--limit限制输出的查询数例如--limit 95%:20表示按响应时间排序把累计到 95% 响应时间的前 20 条输出或者--limit 10表示只输出前 10 条--since/--until只分析指定时间范围内的日志如--since 2026-08-30 14:00:00--filter只处理符合过滤条件的查询事件支持 Perl 表达式--explain对采集到的样例 SQL 执行 EXPLAIN帮助分析执行计划--review/--history将分析结果保存到数据库表中便于长期跟踪--outputjson以 JSON 格式输出分析结果便于程序化处理--type指定输入类型如slowlog、binlog、general、tcpdump等7.5 实战分析慢日志并让工具执行 EXPLAIN如果希望工具自动对代表性 SQL 执行 EXPLAIN以便了解其执行计划可以运行pt-query-digest \ --explain h127.0.0.1,uroot,pyourpassword,Dyourdb \ --limit 80%:10 \ /var/lib/mysql/mysql-slow.log其中--explain需要提供连接参数工具会从慢日志中提取最慢的若干条 SQL连接数据库执行EXPLAIN并在报告中展示执行计划摘要。这可以大幅提升分析效率。7.6 持续跟踪写入历史表有时需要观察慢查询在较长时间段内的趋势变化可以把每次分析结果写入历史表pt-query-digest \ --review h127.0.0.1,Dpercona_schema,tquery_review \ --history h127.0.0.1,Dpercona_schema,tquery_history \ /var/lib/mysql/mysql-slow.log这样每次轮转慢日志后运行一次命令对应的历史表就会累积数据后续可以通过查询query_history表观察每条 SQL 模板的响应时间随时间的变化趋势。8. 慢查询日志与 EXPLAIN 联动定位问题慢查询日志告诉你“哪条 SQL 慢”而EXPLAIN告诉你“它为什么慢”。两者的配合是数据库调优中最经典的工作流。8.1 从慢日志提取 SQL假设从mysqldumpslow或pt-query-digest的分析结果中发现了一条可疑 SQLSELECT user_name, email FROM users WHERE create_time 2026-08-01 ORDER BY id DESC LIMIT 15;慢日志显示它扫描了 98 万行但只返回 15 行。8.2 执行 EXPLAIN 分析将这条 SQL 放到数据库客户端中前面加上EXPLAINEXPLAIN SELECT user_name, email FROM users WHERE create_time 2026-08-01 ORDER BY id DESC LIMIT 15;输出关键几列------------------------------------------------------------------------------------------------------- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | ------------------------------------------------------------------------------------------------------- | 1 | SIMPLE | users | ALL | NULL | NULL | NULL | NULL | 980121 | Using where; Using filesort | -------------------------------------------------------------------------------------------------------从输出可以看到typeALL全表扫描这是最差的访问类型之一。keyNULL没有使用任何索引。rows≈98 万预估扫描行数接近全表行数。Extra 中同时包含 Using where 和 Using filesort先按条件过滤再对结果进行排序。这与慢日志中Rows_examined980000的现象完全吻合问题根源就在于create_time列缺少索引。8.3 常见的 EXPLAIN 特征对照EXPLAIN 特征可能的含义典型优化方向typeALL全表扫描为 WHERE 条件列添加索引typeindex全索引扫描评估是否真的需要扫描整个索引考虑缩小范围或覆盖索引Using filesort需要额外排序让 ORDER BY 列走索引顺序避免临时排序Using temporary使用临时表优化 GROUP BY / DISTINCT减少临时表开销Using index覆盖索引无需回表通常性能较好确认过滤性和选择性即可keyNULL未使用索引检查索引是否缺失或被函数、类型转换破坏8.4 从慢日志反向验证优化效果为create_time添加索引后ALTER TABLE users ADD INDEX idx_create_time (create_time);重新执行 EXPLAINEXPLAIN SELECT user_name, email FROM users WHERE create_time 2026-08-01 ORDER BY id DESC LIMIT 15;此时输出可能变为---------------------------------------------------------------------------------------------------------------------------- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | ---------------------------------------------------------------------------------------------------------------------------- | 1 | SIMPLE | users | range | idx_create_time| idx_create_time | 5 | NULL | 120 | Using index condition; Using filesort | ----------------------------------------------------------------------------------------------------------------------------type从ALL变为range扫描行数从 98 万降到约 120 行性能提升通常是数量级的。此时可以继续观察慢查询日志确认该 SQL 是否不再出现或者执行时间显著下降。9. 慢查询的常见类型与优化策略慢查询日志中的慢 SQL 往往集中在几类典型模式上。熟悉这些模式能让你在看到日志时快速判断根因并采取对应措施。9.1 全表扫描型表现为Rows_examined极大EXPLAIN 显示typeALL。根因通常是查询条件列没有索引或者索引因为隐式类型转换、函数运算而失效。优化方向为 WHERE、JOIN 条件列建立合适的索引。避免在索引列上使用函数例如WHERE DATE(create_time) 2026-08-30应改写为范围查询WHERE create_time 2026-08-30 AND create_time 2026-08-31。避免隐式类型转换例如索引列是字符串而查询条件传入了数字会导致索引失效。9.2 大偏移量分页型典型 SQL 是LIMIT 100000, 20随着偏移量增大MySQL 需要扫描并丢弃前面的大量行执行时间越来越长。慢日志中这类语句的Rows_examined会远大于Rows_sent。优化方向游标式分页基于上次结果的WHERE id last_id ORDER BY id LIMIT 20。先通过覆盖索引查出主键再回表取明细。限制可翻页的最大深度或在业务层做缓存。9.3 排序与分组开销型表现为 EXPLAIN 的 Extra 中出现Using filesort或Using temporary。当 ORDER BY、GROUP BY 的列无法利用索引顺序时MySQL 需要额外的内存或磁盘空间完成排序、分组。优化方向使 ORDER BY 列与 WHERE 条件所走索引保持同序。为 GROUP BY 列建立索引必要时使用覆盖索引减少回表。调整sort_buffer_size等参数但这只是缓解根本还是要优化查询。9.4 关联查询型多表 JOIN 时驱动表选择不当或关联列缺少索引会导致笛卡尔积放大、扫描量暴涨。慢日志中这类语句通常表数量多、执行时间波动大。优化方向为每个关联条件列建立索引。用小结果集驱动大结果集必要时通过STRAIGHT_JOIN干预驱动表选择。拆解大 JOIN把部分关联下推到应用层或使用缓存。9.5 锁等待型如果慢日志中Lock_time在Query_time中占很大比例说明语句主要卡在锁等待上。常见于大事务持有行锁、DDL 持有元数据锁、或者SELECT ... FOR UPDATE长时间未提交。优化方向缩短事务避免在事务中做耗时操作及时提交。合理安排 DDL 执行时间使用pt-online-schema-change或gh-ost等在线变更工具。检查长事务与锁等待关系可用performance_schema中的锁相关表排查。9.6 随机抽样型典型写法是ORDER BY RAND() LIMIT NMySQL 需要对每一行生成随机数再排序当表很大时非常慢。优化方向是改用应用层随机主键抽取或通过WHERE id (SELECT FLOOR(MAX(id)*RAND()) ...)等方式近似实现。9.7 低选择性列索引滥用型有些列虽然建了索引但选择性极低例如性别、状态字段优化器可能放弃索引或者即使走索引也扫大量行。优化手段包括使用组合索引提高选择性、分区或者干脆接受全表扫描但控制表规模。10. 慢查询日志的轮转与维护慢查询日志会随着时间不断增长如果不加管理最终可能占满磁盘分区影响整个 MySQL 实例的运行。因此必须引入轮转机制。10.1 为什么要轮转一个持续开启慢查询日志的生产实例每天可能产生几 GB 甚至更多的日志。长期不清理会带来三类风险磁盘空间耗尽、单个日志文件过大导致分析工具读取缓慢、历史数据混杂使分析结果失去时效性。10.2 使用日志轮转工具Linux 下的logrotate是常用的日志轮转方案。可以创建配置文件/etc/logrotate.d/mysql-slow/var/lib/mysql/mysql-slow.log { daily rotate 7 compress delaycompress missingok notifempty copytruncate create 640 mysql mysql }其中copytruncate是关键配置它先复制日志内容到新文件再清空原文件避免直接mv日志文件后需要向 MySQL 发送 FLUSH 信号才能继续写入的问题。不过copytruncate在复制和清空之间可能丢失极少量写入对于慢查询日志这种可容忍轻微丢失的场景是合适的。10.3 手动轮转如果不使用logrotate也可以手动操作。比较稳妥的做法是轮转后执行日志刷新mv /var/lib/mysql/mysql-slow.log /var/lib/mysql/mysql-slow.log.$(date %F) mysqladmin -u root -p flush-logs其中flush-logs会让 MySQL 重新打开日志文件保证后续日志继续写入新的文件。10.4 定期分析闭环轮转不只是为了节省空间更是为了配合定期分析。建议的节奏是每日或每周轮转一次慢查询日志。轮转后立即用pt-query-digest分析上一周期的日志。将分析报告归档并建立问题 SQL 清单。对 Top SQL 逐个优化验证后关闭对应问题项。11. 慢查询日志参数调优与注意事项11.1 long_query_time 设置多少合适阈值设多大需要结合业务对响应时间的要求。常见的实践是在线交易类业务设置为 1 到 2 秒后台分析类业务可以放宽到 5 到 10 秒。阈值设得太小会导致大量普通 SQL 被记录产生噪音设得太大则会漏掉一些需要关注的慢 SQL。此外long_query_time修改后对已存在的连接不生效的问题在排查时容易被忽略实际使用中建议修改配置后重启应用连接池或等待连接重建。11.2 监控发送到表中的日志量如果选择log_outputTABLE务必定期监控mysql.slow_log表的大小和行数。该表使用 CSV 存储引擎时性能相对有限写入频率过高可能成为新的性能负担。MySQL 8.0 中该表使用 InnoDB 引擎情况有所改善但仍要注意磁盘空间。SELECT COUNT(*), ROUND(SUM(LENGTH(sql_text))/1024/1024, 2) AS total_mb FROM mysql.slow_log;11.3 与通用查询日志的区别不要混淆慢查询日志和通用查询日志General Query Log。通用查询日志记录的是所有到达服务器的 SQL 语句数据量巨大通常只在极短时间的调试中开启而慢查询日志只记录超过阈值的语句体积相对可控。二者定位不同通用日志看“全部”慢日志看“低效”。11.4 权限要求开启慢查询日志、查看相关变量和日志文件需要具备SUPER或 MySQL 8.0 中的SYSTEM_VARIABLES_ADMIN、CONNECTION_ADMIN等相关权限。在云数据库或托管服务中这些能力通常通过控制台开关和日志下载功能提供。12. 一个完整的慢查询定位实战案例下面通过一个虚拟但贴近真实业务的案例完整演示从发现慢查询到完成优化的过程。12.1 背景某电商系统的订单列表接口在促销期间出现超时平均响应时间从 200ms 恶化到 3 秒以上。DBA 首先确认慢查询日志已开启阈值设置为 1 秒。12.2 分析日志使用mysqldumpslow快速查看 Top 5mysqldumpslow -s t -t 5 /var/lib/mysql/mysql-slow.log输出显示出现次数最多、累计耗时最长的是一条订单查询SELECT o.order_no, o.amount, u.name, o.create_time FROM orders o JOIN users u ON o.user_id u.id WHERE o.status paid AND o.create_time 2026-08-01 ORDER BY o.create_time DESC LIMIT 20;慢日志显示Query_time平均 3.2 秒Rows_examined平均 80 万。12.3 EXPLAIN 定位执行 EXPLAINEXPLAIN SELECT o.order_no, o.amount, u.name, o.create_time FROM orders o JOIN users u ON o.user_id u.id WHERE o.status paid AND o.create_time 2026-08-01 ORDER BY o.create_time DESC LIMIT 20;结果摘要--------------------------------------------------------------------------------------------------------------------------- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | --------------------------------------------------------------------------------------------------------------------------- | 1 | SIMPLE | o | ALL | NULL | NULL | NULL | NULL | 850000 | Using where; Using filesort | | 1 | SIMPLE | u | eq_ref | PRIMARY | PRIMARY | 4 | db.o.user_id | 1 | NULL | ---------------------------------------------------------------------------------------------------------------------------可以看到orders表走了全表扫描说明status和create_time上没有合适的索引或者单独索引不能满足组合条件。12.4 制定优化方案查看orders表当前索引SHOW INDEX FROM orders;发现只有主键id和user_id上的普通索引。于是创建组合索引将过滤性最高的等值条件放在最前范围条件放在其后并与排序列顺序保持一致ALTER TABLE orders ADD INDEX idx_status_create_time (status, create_time);12.5 验证优化效果再次执行 EXPLAINEXPLAIN SELECT o.order_no, o.amount, u.name, o.create_time FROM orders o JOIN users u ON o.user_id u.id WHERE o.status paid AND o.create_time 2026-08-01 ORDER BY o.create_time DESC LIMIT 20;结果摘要--------------------------------------------------------------------------------------------------------------------------------- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | --------------------------------------------------------------------------------------------------------------------------------- | 1 | SIMPLE | o | range | idx_status_create_time | idx_status_create_time | 9 | NULL | 37 | Using where | | 1 | SIMPLE | u | eq_ref | PRIMARY | PRIMARY | 4 | db.o.user_id | 1 | NULL | ---------------------------------------------------------------------------------------------------------------------------------orders表的访问类型从ALL变为range扫描行数从 85 万降到约 37 行。实际业务验证显示接口响应时间恢复到 200ms 以内且后续慢查询日志中该 SQL 不再出现。12.6 案例总结这个案例完整展示了“慢日志发现问题 → mysqldumpslow 汇总 → EXPLAIN 定位根因 → 建索引 → 验证效果 → 观察慢日志闭环”的标准流程。整个过程高度依赖慢查询日志提供的客观数据避免了对性能问题的主观臆测。13. 慢查询日志的替代与补充手段慢查询日志虽好但并非唯一的手段。在实际工作中它需要与其他监控和诊断工具配合使用。13.1 Performance Schemaperformance_schema提供了更细粒度、更实时的语句执行统计。通过events_statements_summary_by_digest表可以查询到最近一段时间内所有 SQL 的累计执行次数、平均耗时、最大耗时等指标且不需要开启慢查询日志。SELECT DIGEST_TEXT, COUNT_STAR AS exec_count, AVG_TIMER_WAIT/1000000000000 AS avg_sec, MAX_TIMER_WAIT/1000000000000 AS max_sec FROM performance_schema.events_statements_summary_by_digest ORDER BY AVG_TIMER_WAIT DESC LIMIT 20;它的优势是实时性强、开销可控还能看到语句的执行计划摘要。缺点是数据只保留在内存中实例重启后清零不适合长期存档。13.2 监控平台Prometheus Grafana、Zabbix、慢日志采集 Agent 等方案可以将慢查询日志和实例监控指标统一展示对慢查询做告警。例如当某条 SQL 的响应时间在 5 分钟内持续超过 2 秒时触发告警让 DBA 提前介入而不是等业务投诉。13.3 数据库审计与在线 SQL 分析部分云数据库和商业产品提供 SQL 洞察、极速分析等功能能够在控制台直接展示慢 SQL 排名、执行计划、优化建议。其本质仍然是基于慢查询日志或 Performance Schema 的封装理解底层原理有助于更好地使用这些工具。14. 常见问题排查 FAQ14.1 慢查询日志为空但确实有慢 SQL可能的原因包括slow_query_log未开启或开启后又因重启丢失。SQL 执行时间恰好等于long_query_time未严格超过阈值。min_examined_row_limit设置过大扫描行数未达标的语句被过滤。观察的是错误文件路径实际日志写到了其他位置。在从库上执行的语句未开启log_slow_replica_statements。14.2 慢查询日志增长过快淹没有效信息可以采取的措施适当调大long_query_time聚焦真正严重的慢 SQL。为“未使用索引”的日志设置log_throttle_queries_not_using_indexes速率限制。使用pt-query-digest对日志做指纹聚合避免被重复 SQL 淹没。将日志轮转策略调整为按天或按大小轮转。14.3 修改 long_query_time 后不生效常见原因是修改只对新建连接生效而应用使用的连接池仍然持有旧连接。解决方法是让连接池分批重建连接或者重启应用或者直接修改配置文件并重启数据库实例。14.4 慢日志里出现大量相同 SQL如何判断重要性单看一条出现次数多的慢 SQL不能只看单次耗时还要看累计耗时和业务影响。有的语句单次只慢 0.5 秒但每分钟执行上千次累计占用大量资源同样需要优化。用pt-query-digest的Calls和Response time两列综合评估即可。14.5 日志文件写入性能开销有多大正常配置下慢查询日志的写入开销很小因为只有超过阈值的语句才会记录。但如果阈值极低、未使用索引的日志未做限流、且实例 QPS 很高写入量会明显上升可能对磁盘 I/O 产生压力。建议对慢日志所在磁盘的 IOPS 和空间使用率做监控。15. 总结与最佳实践清单慢查询日志是 MySQL 性能调优的核心工具之一它的价值不在于“记录”而在于“分析”。只有把日志数据转化为可执行的优化动作才能真正解决性能问题。以下是建议遵循的最佳实践清单开启并参数化生产环境开启慢查询日志合理设置long_query_time日志输出到独立磁盘的文件。控制日志噪音按需开启“未使用索引”记录并配合限流参数避免日志爆炸。建立轮转机制用logrotate等工具按天轮转、压缩、归档定期清理过期文件。定期分析闭环每天或每周用pt-query-digest分析慢日志产出 Top SQL 清单并跟踪处理进度。结合 EXPLAIN 定位对重点慢 SQL 执行 EXPLAIN依据访问类型、扫描行数、Extra 信息判断根因。验证优化效果优化后通过执行时间和慢日志出现频率复核效果做到“优化有据、效果可量化”。配合监控告警将慢查询趋势纳入监控体系设置阈值告警变被动排查为主动预防。持续迭代业务和数据量是动态变化的慢查询优化不是一次性工作而是一个持续迭代的过程。掌握了慢查询日志的开启、解析、分析工具和优化方法论你就能在大多数 MySQL 性能问题面前做到“有据可查、有法可解”从被动救火走向主动治理。
返回列表