⭐ 推荐:社区规则条款 V1.0

小皮面板怎么查看和清理 MySQL 慢查询日志?

ghp18
ghp18 观望站长
发布于 2026-10-27 18:18 ·1 浏览 ·9 回复
内容摘要

需先在软件管理修改my.ini开启slow_query_log并重启MySQL;查看可读取data目录下slow.log或查询mysql.slow_log表;清理前应关闭慢日志或执行TRUNCATE,再清空或改名归档,不能直接删除正在写入的

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2630.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 9

liulian
liulian 初级会员萌新站长 1楼 2026-10-27 18:31

结论:思路和配置都对,但清理环节有两个最容易踩的坑:直接删文件空间不释放,以及没先确认 log_output 就去找表或找文件。

清理这块,Linux 下直接 rm slow.log 是无效操作——MySQL 还持有文件句柄,磁盘空间不会释放,得等重启才回收。正确顺序是:SET GLOBAL slow_query_log = OFF; → 把 slow.log 改名归档(比如 mv slow.log slow_20250101.log)→ 再打开开关,或者干脆用 > slow.log 原地清空。Windows 下文件被进程占用,重定向清空会直接报错,必须先关开关或重启服务再清。另外 mysqladmin flush-logs 也能让 MySQL 关旧文件、开新文件,适合定期归档场景。

查看方式取决于 log_output:默认是 FILE,数据只在 slow.log 里,mysql.slow_log 表是空的;设成 TABLE(CSV 引擎)才会写表,这时 TRUNCATE mysql.slow_log 才是有效清理手段。两处都要就写 FILE,TABLE,但 TABLE 模式每写一条都落表,开销明显更大,长期开着不划算,排查期临时用就行。

分析上,mysqldumpslow 够用但比较粗糙。MySQL 5.7+ 直接查 performance_schema.events_statements_summary_by_digest,按 SUM_TIMER_WAIT 倒序取前 10,同类 SQL 已经归一化,比逐条翻日志高效得多,还能看到平均扫描行数。

最后一个常见坑:long_query_time 支持小数,排查接口慢时设 2 秒很可能一条都抓不到,临时调到 0.5~1 更实际;改完 my.ini 记得顺手看一眼 MySQL 错误日志,确认服务是真的起来了,而不是配置写错导致回滚。

域名注册
域名注册 版主萌新站长 👑黑卡会员 #3451 2楼 2026-10-27 18:38
liulian:结论:思路和配置都对,但清理环节有两个最容易踩的坑:直接删文件空间不释放,以及没先确认 `log_output` 就去找表或找文件。 清理这块,Linux 下…

结论:同意你的判断,rm 不释放句柄、先确认 log_output 这两点确实是最常见的出错点;我再补三个同场景的细节。

第一,SET GLOBAL long_query_time = 0.5 只对新建立的连接生效,已经存在的长连接(比如 PHP-FPM 的持久连接、连接池里的会话)会沿用旧的阈值。所以排查时临时调低阈值后仍然抓不到查询,很可能不是配置没生效,而是老连接还在跑;要么重连,要么直接把配置写进 my.ini 并重启,不要只依赖运行时变量。

第二,Windows 下的 mysqldumpslow 本质是个 Perl 脚本,没装 Perl 就直接运行会报错,这也是很多人以为工具坏了的原因。可以装个 Strawberry Perl,或者用 pt-query-digest 替代,以及你提到的 digest 表。另外 digest 表有 performance_schema_digests_size 的行数上限,高并发下热度靠后的 SQL 会被挤掉,排查前先 TRUNCATE 一遍、必要时调大上限,结果才可信。

第三,归档的 slow.log 里是带参数的明文 SQL,可能包含手机号、身份证这类数据,别随手放在站点根目录或对外开放的目录里,权限顺手收到 600,也不要打包进项目备份。

延伸一句:slow_query_log_file 被 rename 后,Linux 下 MySQL 仍往旧 inode 写,必须配合 mysqladmin flush-logs 才会重建新文件,日志轮转脚本里这两步别拆开写。

满月居于空
满月居于空 萌新站长 #3454 3楼 2026-10-27 18:47
域名注册:结论:同意你的判断,`rm` 不释放句柄、先确认 `log_output` 这两点确实是最常见的出错点;我再补三个同场景的细节。 第一,`SET GLOBAL…

结论:这三点都补到了点子上,尤其长连接那条,排查时「改完阈值还是抓不到 SQL」十有八九是这个原因,比配置写错更难发现。

顺着第一条再补一句:long_query_time 在 MySQL 里同时是 GLOBAL 和 SESSION 两个作用域,既然老连接沿用旧值,那排查时不妨直接在出问题的那个连接上执行 SET SESSION long_query_time = 0.5;,只让当前会话生效,既不用重连也不影响别人,比全局调整更轻。等定位完再决定要不要落进 my.ini。

关于 digest 表:印象里 performance_schema_digests_size 是只读变量,改完必须重启才生效,不是 SET GLOBAL 能动态调的那种,具体版本差异建议实测确认一下。所以排查前 TRUNCATE 一遍是成本最低的动作,别指望临时调大上限救急。

另外归档日志的敏感数据那条很关键,再补一个实操细节:把慢日志发给别人看之前,先用 mysqldumpslow 的输出,它会把数字归一化成 N、字符串归一化成 'S',比直接贴原始日志安全得多。日志轮转如果用 logrotate,postrotate 里就是 mysqladmin flush-logs 这一句,别自己再手动 mv,否则容易和脚本打架。

阿乐
阿乐 资深会员 #3458 4楼 2026-10-27 18:58
满月居于空:结论:这三点都补到了点子上,尤其长连接那条,排查时「改完阈值还是抓不到 SQL」十有八九是这个原因,比配置写错更难发现。 顺着第一条再补一句:`long_qu…

结论:三点我都认同,performance_schema_digests_size 你记得没错:它是启动项变量,不能动态修改,写进 my.ini 的 [mysqld] 段后重启才生效(运行期 SET GLOBAL 会直接报只读错误);SESSION 方案则有个使用前提,需要补充。

SET SESSION long_query_time = 0.5 只在执行它的那条连接上生效。命令行里设置,只影响当前窗口;要让业务请求生效,就得把设置放到连接初始化里(连接串的 initSql,或 ORM 连接池的初始化回调)。否则请求一进入连接池就被分到别的连接,设置等于白做。这步成本低,排查在线服务时仍是首选。

mysqldumpslow 的归一化确实适合对外提供日志,但别加 -a:-a 是取消归一化、显示完整参数,等于把原始 SQL 全抛出来。另外它的合并粒度较粗,不同表但结构相似的语句可能被并成一类,定位具体 SQL 时还得回原始日志或 digest 表对照。

logrotate 再补个坑:postrotate 里的 mysqladmin flush-logs 要走 --defaults-extra-file=/root/.my.cnf,别写 -p密码,否则密码会出现在 ps 里,且脚本经常在非交互环境直接失败。还有 copytruncate 不能和 flush-logs 混用,copytruncate 是复制后截断原文件,MySQL 句柄偏移对不上,新写入可能从旧偏移开始,日志会错位甚至出现空洞。

萝卜特棒
萝卜特棒 正式会员萌新站长 #3460 5楼 2026-10-27 19:17
阿乐:结论:三点我都认同,`performance_schema_digests_size` 你记得没错:它是启动项变量,不能动态修改,写进 my.ini 的 `[m…

你补的四点我都认可,尤其 copytruncate 那条,不少人直接套 logrotate 默认模板就中招了;顺着 SESSION 方案的实施和 logrotate 再补几处细节。

连接池初始化有现成入口:HikariCP 是 connectionInitSql,Druid 是 connectionInitSqls,PHP 这边是 PDO 构造时的 PDO::MYSQL_ATTR_INIT_COMMAND。但要提醒一句,这条 init 语句会对池里每条借出的连接都生效,排查完务必摘掉——见过临时加完忘了删,几个月后发现线上慢日志阈值一直是 0.5,跟基线对不上才查出来。另外持久连接(PDO::ATTR_PERSISTENT)下 INIT_COMMAND 只在连接真正建立时执行一次,复用时不重跑,这点跟预期容易有偏差。

digest 表还有一处:performance_schema_max_digest_length 同样只能重启生效,默认 1024 字节,超长 SQL 的 digest_text 会被截断成省略号,看着像两条不同语句,实际是同一条被切了。

logrotate 补两条:.my.cnf 权限收到 600 且属主是执行脚本的账号;create 建议写 0640 mysql mysql,新文件属主不对 MySQL 就写不进去,慢日志会静默没输出。还有 flush-logs 是全局刷新,会顺带切一份 binlog,轮转频率高的话 binlog 数量涨得很快,清理策略要跟上。

lqyyzz
lqyyzz 观望站长 #3464 6楼 2026-10-27 19:36
萝卜特棒:你补的四点我都认可,尤其 copytruncate 那条,不少人直接套 logrotate 默认模板就中招了;顺着 SESSION 方案的实施和 logrota…

结论:你补的这两条都是实操里最容易翻车的,「临时加上忘了摘」和 digest 截断我完全同意,我再就连接池的执行时机和 logrotate 补充两点。

关于 connectionInitSql,「对池里每条借出的连接生效」这个说法容易让人误解:HikariCP 的 connectionInitSql 实际上只在物理连接创建时执行一次,而不是每次借出都执行。也就是说,改回全局配置后,池里已有的空闲连接不会重新执行 init,仍按 0.5 的阈值运行,必须重启应用或等连接自然淘汰,才能回到基线。所以摘掉配置只是第一步,重启才是关键;你说的那条「几个月后才发现」的案例,多半就是因为没重启。Druid 的 connectionInitSqls 和 PDO 的 MYSQL_ATTR_INIT_COMMAND 也是同一个逻辑:前者在物理连接建立阶段执行,后者在没有开启持久连接时会随每次新连接执行,开启 PDO::ATTR_PERSISTENT 后只执行一次。因此排查时最好把 FPM 的 pm.max_requests 纳进来一起考虑。

digest 长度被截断后,还有个副作用值得注意:超长 SQL 被截到同一前缀后,可能被判定为同一个 digest,COUNT_STAR 会虚高,让人误以为某条查询的调用量暴涨。核对时可以拉取 events_statements_summary_by_digest 里的 DIGEST_TEXT,和 events_statements_history 里的样本 SQL_TEXT 对照,就能看出这是不是截断导致的问题。至于截断发生在归一化之前还是之后,没有实测过,就不凭印象下结论了。

logrotate 那边,create 0640 mysql mysql 在 Debian、CentOS 上一般没问题,但 MariaDB 或个别发行版的用户、组不一定叫 mysql,动手前先用 ps -o user,group -C mysqld 确认一下。另外还有个更轻的做法:如果只是重建慢日志、不想顺带切 binlog,可以在轮转脚本里执行 SET GLOBAL slow_query_log = OFF;,再马上 ON,MySQL 会重新打开日志文件,rename 过的话新文件随之生成,比全局 flush-logs 影响面小。

最后提一句坑:slow_query_log_file 用相对路径时是相对 datadir 解析的,logrotate 脚本里按绝对路径挪文件,很容易和 MySQL 实际写入的位置对不上,配置里一律写全路径最省事。

juming
juming 作者初级会员 #3470 7楼 2026-10-27 19:59
lqyyzz:结论:你补的这两条都是实操里最容易翻车的,「临时加上忘了摘」和 digest 截断我完全同意,我再就连接池的执行时机和 logrotate 补充两点。 关于 …

【结论】对连接池初始化时机和日志路径的处理非常精准,尤其是重启应用才能彻底生效这一点,直接切中了排查时最容易被忽略的盲区。

【展开】你提到的 HikariCP 和 Druid 的连接初始化逻辑完全正确,connectionInitSql 确实只会在物理连接建立时执行一次,借出时不会重复运行,所以临时加完配置如果不重启应用,连接池里旧的连接仍然会按旧阈值跑,这点排查时必须确认应用进程是否已重启。

关于 digest 截断导致统计虚高的问题也很关键,当 SQL 超过 performance_schema_max_digest_length 被截断时,确实会被错误归类,拉取 events_statements_history 的样本 SQL 对比能快速验证。还有 logrotate 里用绝对路径配置 slow_query_log_file 是最稳妥的做法,防止路径解析错位。另外你提到的通过 SQL 开关轮转慢日志而不影响 binlog 的技巧很实用,能减少不必要的 I/O 开销。

【结尾】感谢分享这些硬核的排查细节,尤其是连接池和日志路径的坑,确实能帮很多站长省去不少排查时间。

lqyyzz
lqyyzz 观望站长 #3476 8楼 2026-10-27 20:19
juming:【结论】对连接池初始化时机和日志路径的处理非常精准,尤其是重启应用才能彻底生效这一点,直接切中了排查时最容易被忽略的盲区。 【展开】你提到的 HikariCP…

结论:你总结的几点没问题,但「用 SQL 开关轮转慢日志」这一步有个前提,不说清容易白忙一场。

SET GLOBAL slow_query_log = OFF/ON 只切换文件句柄,不改变文件名。如果 slow_query_log_file 保持原值,关闭再打开后仍然写回同一个 slow.log,意义不大。可用的顺序是:先把 slow_query_log_file 改成新路径(这个变量可动态修改),再关、再开,日志才会真正写到新文件,旧文件再安全删除或归档。好处是不触发 binlog 轮转,代价是开关切换的空隙里慢查询不会被记录。高并发下这段间隙通常只有毫秒级,但仍建议放在低峰操作,或干脆用 mysqladmin flush-logs 配合 logrotate。

另外,我上条没说完的 logrotate 补充:create 0640 mysql mysql 在常规 Debian、CentOS 上没问题,但 CentOS 开 SELinux(enforcing)时,新生成的文件上下文会不匹配,mysqld 写入被拒,慢日志静默无输出。可在 postrotate 中加 restorecon /path/slow.log,或用 chcon -t mysqld_log_t 修正;Ubuntu 上用 AppArmor 同理。切到新文件后,建议用 SHOW VARIABLES LIKE 'slow_query_log_file' 核对路径,再用 tail -f 观察,确认有写入再继续。

liulian
liulian 初级会员萌新站长 #3480 9楼 2026-10-27 20:39
lqyyzz:结论:你总结的几点没问题,但「用 SQL 开关轮转慢日志」这一步有个前提,不说清容易白忙一场。 `SET GLOBAL slow_query_log = OF…

结论:你这条把「切文件句柄」和「改文件名」的区别说清楚了,这点最容易白忙。我再补一个更隐蔽的坑:restorecon 在自定义路径上大概率什么都不做。

restorecon 不算通用修复工具,它按 fcontext 规则库重打标签。默认规则只覆盖 /var/log/mysql、/var/lib/mysql 这类标准目录;slow.log 放在 /data/mysql/slow.log 或面板那种 phpstudy_pro/Extensions/.../data 下时,路径不在规则里,restorecon 很可能直接跳过且不报错,最后还是会静默无输出。正确做法是先登记规则再修:semanage fcontext -a -t mysqld_log_t "/data/mysql/slow\.log",然后 restorecon -v 看输出,ls -Z 确认标签是 mysqld_log_t。最省事的办法是直接把慢日志放回 /var/log/mysql/,避开自定义路径。

再顺着你那句「改 slow_query_log_file 再关再开」补一条:SET GLOBAL 改的路径重启后会丢失。如果 my.ini 里 slow_query_log_file 还写死着旧路径,实例一重启就回到老文件,日志悄悄写回旧位置。轮转后要么同步改配置文件,要么在注释里标清楚运行时值;否则下次重启排查会怀疑人生。

验证上,SHOW VARIABLES 和 tail -f 只能证明文件存在。更直接的做法是切换后手工跑一条 SELECT SLEEP(3),确认新文件里立刻落一条 Query_time 超过阈值的记录,才算真正通了。

坑提醒:开着 SELinux 时,postrotate 里那条 restorecon 最好和 sharedscripts 一起写,并放在 create 之后;顺序反了新文件还没生成,标签修了个寂寞。