MySQL general log:通用查询日志开启、分析、性能代价与排障实践

凌晨两点被电话叫起来,最怕听见一句“库好像被人动过”。那种时候,慢查询日志帮不上忙,因为执行得快的语句它根本不记;二进制日志只记改了数据的语句,谁连上来、谁执行了 SELECT、谁悄悄改了个会话变量,一概看不见。真正能把“谁在什么时候、用什么账号、从哪台机器、发了哪条语句”完整串起来的,只有MySQL 的日志里最朴素也最暴力的那一个:general log(通用查询日志)。它把服务器收到的每一条指令原样落到文件或表里,像一台不关机的行车记录仪。

这篇东西写给两类人:一类是正在排查“到底是谁干了这件事”的运维和 DBA,另一类是想搞清楚 general log 到底能不能开、开了会不会把数据库拖垮的开发同学。我会把它记录什么、怎么开、怎么关、怎么分析、代价多大、哪里会踩坑,一条条摊开讲。它不是“打开开关就完事”的配置项,开错方式、开错时长、开错输出目标,任何一环都能让一台本来跑得好好的库在半小时内变得很卡,或者让磁盘在某天早上突然写满。下面这些内容,大部分是我自己在测试库和预发环境里反复折腾出来的,也有一些是替别人收拾现场时总结的。

1. 先搞清楚 general log 到底记录了什么

1.1 一次“抓不出凶手”的排查把我逼回日志开关

早些年遇到过一个很典型的场景:某业务系统的测试库里,一张配置表的关键字段在非发布时段被改过,数值不大,但影响了第二天的一批对账任务。当时手上能用的材料只有二进制日志和慢查询日志。二进制日志确实记录了那条 UPDATE,可它只告诉我“几点几分执行了什么 SQL”,至于这条语句是哪个账号发的、从哪台应用服务器连过来的、在此之前这个会话还执行过什么,全都没有。慢查询日志更没用,那条 UPDATE 走的是主键,毫秒级完成,压根进不了慢日志的门槛。

那次之后我才真正把 general log 当成一件正经工具来用。它的定位很明确:记录 MySQL 服务器收到的每一条顶层指令,包括连接建立、切换数据库、执行查询、预处理语句的 prepare 和 execute、连接断开。它不是审计系统,不具备防篡改能力,也不做权限校验,但它足够“全”,全到你可以拿它还原一个会话的完整行为轨迹。这一点是其他任何日志都替代不了的。

需要提前说清楚的是它的定位边界。general log 记录的是“服务器收到了什么”,而不是“这条语句最终返回了什么”。你看得到SELECT * FROM t WHERE id = 1,看不到它返回了几行、耗时多少毫秒。想看耗时得配合慢日志或者 performance_schema,想看返回行数得靠别的观测手段。很多人第一次用它的时候会误以为这是一份“完整的执行档案”,实际上它更像一份“请求流水”,只记入口不记出口。

1.2 记录格式与字段逐项拆解

开启之后,无论是写文件还是写表,一条记录的构成是固定的。表格模式下字段最清楚,mysql.general_log的结构大致是这样:

字段类型含义实际取值举例
event_timetimestamp语句被服务器接收的时间2024-06-11 02:17:43
user_hostmediumtext账号名 + 客户端来源app_ro[app_ro] @ [10.20.3.51]
thread_idbigint会话线程号,同一连接内保持不变88423
server_idint实例标识,主从环境里区分来源1
command_typevarchar(64)指令类型Query / Connect / Quit / Prepare
argumentmediumtext指令正文select * from t_config where id=1

文件模式下的格式略有不同,是一条以时间开头、字段用制表符分隔的文本行,形如:

2024-06-11T02:17:43.512345+08:00 88423 Query select * from t_config where id=1

command_type这一列值得单独说,因为它决定了你能看到多少信息。常见取值包括Connect(客户端连上来)、Query(普通文本协议语句)、Prepare与Execute(预处理语句的两个阶段)、Quit(正常断开)、Init DB(切换默认库)、Field List(某些驱动取元数据时触发)。这里有个特别容易误解的点:使用预处理协议时,参数值不一定出现在Query里。如果你看到一堆Execute后面跟着SELECT * FROM t WHERE id = ?,别急着说日志没记全,参数是在Prepare阶段和Execute的 argument 里以另一种形式带出来的,具体呈现取决于驱动版本和协议细节。排查时如果发现参数缺失,第一反应应该是去看同一thread_id前后几行,而不是怀疑日志坏了。

还有一类内容是不记录的,这点必须提前知道,否则会白折腾。认证失败的连接不进 general log,它进的是错误日志;服务器内部的复制线程执行的部分语句、存储过程内部的每条子语句(取决于版本和log_output组合),呈现方式也和你预期不同;至于SHOW类语句被驱动自动补发的元数据查询,会以Field List之类的形式出现,看着杂乱但都是正常现象。

1.3 它和慢日志、错误日志、二进制日志的分工

把这四种日志放在一张表里比一比,思路会清楚很多。

日志类型记录内容触发条件典型用途体积
general log所有收到的指令开关打开即全量记录还原会话行为、定位来源极大
慢查询日志执行时间超阈值的语句超过 long_query_time性能优化中等可控
错误日志启动、崩溃、认证失败等事件事件发生时故障定位很小
二进制日志变更数据的语句或行镜像写入类操作提交时复制、恢复大但可管理

从这张表能看出 general log 的独特性:它是唯一一个“不挑食”的日志。慢日志挑慢的,二进制日志挑改数据的,错误日志挑异常的,只有它什么都收。这份“什么都收”换来的代价就是体积和性能开销,这也是为什么几乎所有生产规范里都写着“禁止长期开启 general log”。它不是不能开,而是不能一直开。把它当成临时探针,用完就拔,才是正确用法。

我在带新人的时候经常打个比方:慢日志像体检报告,只告诉你哪项指标超标;二进制日志像账本,只记钱的进出;general log 像监控录像,什么都在里面,但你不会为了看一次丢东西把一年的录像全存下来。

2. 打开日志前的三个决定:输出方式、路径、权限

2.1 log_output:文件还是表,这是第一个分水岭

开启 general log 之前必须先想清楚输出到哪里,这个由log_output变量决定。它支持三个值:FILE、TABLE、NONE,而且是可以组合的,比如SET GLOBAL log_output = 'FILE,TABLE'就表示两边都写。默认值是FILE。

这两条路的差别非常大。写文件是顺序追加,走的是文件系统的缓冲,成本相对可控;写表是把记录插进mysql.general_log,而这张表默认使用 CSV 存储引擎,不支持索引,每次写入都要打开文件、写入、关闭,并且是在全局日志锁的保护下进行的。在低并发的手工测试环境里你感觉不到差别,一旦 QPS 上去,表模式的写入延迟会非常刺眼。我做过一个很粗糙的对比,在同样的语句压力下,文件模式的实例 TPS 下滑大概在两成以内,表模式的实例下滑能到一半甚至更多,而且响应时间的毛刺明显变多。

所以我的建议很直接:生产或压测环境只用 FILE,TABLE 留给单机排查和演示。表模式唯一的好处是可以用 SQL 直接查,配合WHERE条件过滤,但因为它没有索引,表一大查询本身就成了负担,查一次全表扫描几百万行,反而给实例添乱。想用 SQL 查,不如先写文件再用 shell 处理,效率高得多。

还有一点容易忽略:log_output是全局变量,改了会影响所有日志的输出目标,包括慢日志。如果你只想让 general log 写表而让慢日志继续写文件,那是做不到的,两者共用这一个开关。这也是为什么很多人改完log_output之后发现慢日志不见了,回头还得改回来。

2.2 参数清单与动态开关的写法

相关参数就那几个,列清楚比翻文档快:

参数作用域默认值说明
general_log全局OFF总开关,0/1 或 OFF/ON
general_log_file全局主机名.log文件路径,相对路径基于 datadir
log_output全局FILE输出目标,可组合

开启和关闭的写法有两种,效果一样,看习惯:

-- 方式一:布尔语义 SET GLOBAL general_log = ON; SET GLOBAL general_log = OFF; -- 方式二:数值语义 SET GLOBAL general_log = 1; SET GLOBAL general_log = 0;

指定文件路径的时候要注意,general_log_file修改后,如果日志当前是开着的,新路径不会立刻生效,需要执行一次日志刷新:

SET GLOBAL general_log_file = '/data/mysql/logs/general_probe.log'; FLUSH GENERAL LOGS;

FLUSH GENERAL LOGS这个语法在较新的版本里才支持细分,老版本上直接用FLUSH LOGS也行,它会把二进制日志、general log、慢日志一起轮转。这里有个坑:FLUSH LOGS在开启 GTID 的实例上会切一个新的二进制日志文件,如果你只是想在排查期间单独轮转 general log,用细分的FLUSH GENERAL LOGS更稳妥,避免顺手把复制链路搅乱。

还需要确认一个版本差异:general_log是全局变量,不支持会话级设置。你执行SET SESSION general_log = ON会直接报错,提示它是全局变量。这意味着只要开关一开,全实例所有连接都会被记录,没有“只记某个库、只记某个用户”的内置能力。想缩小范围,只能从时间窗口入手,或者干脆用别的方案,这一点后面会专门讲。

2.3 谁能改这个开关:权限与会话影响面

改general_log需要相当高的权限。老版本上是SUPER,新版本把系统变量的管理权限拆开了,需要SYSTEM_VARIABLES_ADMIN或者等价的动态权限。很多团队给业务账号配的是常规读写权限,改不了这个开关,必须用管理账号操作。

这里有个操作习惯问题值得强调。我见过有人在业务低峰期用高权限账号打开日志,然后忘了关,也忘了自己是用哪个终端开的。三天后磁盘告警,登录一看日志文件几十个 G。所以我的做法是:开日志的同时就在同一个终端里把关闭命令和一条定时清理命令一起写好,甚至直接在一个带timeout的脚本里执行,避免人肉记忆失效。

另外提醒一句,打开 general log 会对所有会话产生可观测的影响——不是功能影响,是延迟影响。有些对延迟极敏感的应用(比如带严格超时的接口)在日志开启期间会出现零星超时,尤其是在表模式下。动手之前跟业务方打个招呼,比事后解释要省事得多。

3. 手把手实操:从开启到回收的完整闭环

3.1 开之前先算账:磁盘与 QPS 基线

我从来不建议“先开了再说”。开之前花五分钟算一笔账,能避免后面一小时的救火。

先看当前实例的语句吞吐。可以从状态变量里读一个粗略的基线:

SHOW GLOBAL STATUS LIKE 'Questions'; -- 隔 10 秒再读一次,两次相减除以 10 就是近似 QPS SHOW GLOBAL STATUS LIKE 'Questions';

假设算出来是 800 QPS,也就是说平均每秒有 800 条指令进来。一条 general log 记录的长度和语句长度正相关,短语句大概 100 到 150 字节,带长IN列表或者大文本的语句能到几 KB。按平均 200 字节保守估算:

800 条/秒 × 200 字节 ≈ 160 KB/s 160 KB/s × 3600 秒 ≈ 576 MB/小时

也就是说,一个中低负载的实例,开一小时大概写半 GB 到 1 GB。高并发实例上翻十倍都不奇怪。所以动手前先确认日志目录所在分区还剩多少空间,并且把“开启时长”写进计划里,比如“只开 15 分钟,抓完就关”。这个数字比任何参数都重要,因为 general log 的失控方式从来不是性能,而是磁盘。

顺便确认一下当前状态,避免重复操作或者误判:

SHOW VARIABLES LIKE 'general_log%'; SHOW VARIABLES LIKE 'log_output'; SHOW VARIABLES LIKE 'datadir';

如果log_output是TABLE,而你要往文件写,先改回FILE,顺序别搞反。

3.2 复现验证:一条 SQL 从连接到执行的完整记录

真正的验证不能只看“文件有没有变大”,要让日志里出现一段你能对得上的完整轨迹。我一般会这么做:新开一个终端连接上去,依次执行连接、切库、查询、再断开,然后去看日志里这几行是否齐全。

mysql -h 127.0.0.1 -P 3306 -u probe_user -p

进去之后:

USE demo_db; SELECT id, cfg_value FROM t_config WHERE id = 1;

退出。然后打开日志文件看尾部:

tail -n 20 /data/mysql/logs/general_probe.log

正常情况下你能看到类似这样的几行:

2024-06-11T02:17:41.203118+08:00 88423 Connect probe_user@localhost on using TCP/IP 2024-06-11T02:17:41.204779+08:00 88423 Query select @@version_comment limit 1 2024-06-11T02:17:44.881003+08:00 88423 Query SELECT id, cfg_value FROM t_config WHERE id = 1 2024-06-11T02:17:46.117640+08:00 88423 Quit

这段记录的价值在于thread_id全程一致(88423),时间戳连续,Connect行里带了客户端来源。排查“是哪台机器干的”时,这一行就是关键证据。如果日志里只有Query没有Connect,那多半是你在连接建立之后才打开的开关,前面那段自然没记上。

验证的时候还有个细节可以顺便确认:Connect那一行的来源信息里,如果客户端走的是本机 socket,显示的是localhost;走 TCP 则是对端 IP。这两者在排查时意义不同,别混着看。

3.3 三种关闭与清理方式(文件轮转、表清空)

抓完数据,接下来是收尾。文件模式的收尾相对简单,但别直接rm,因为 mysqld 还握着这个文件句柄,删了只是目录项没了,空间不会立刻释放,得等进程重启或者重新打开文件。正确姿势是三步:

# 1. 先把当前文件改名挪走 mv /data/mysql/logs/general_probe.log /data/mysql/logs/general_probe.log.bak # 2. 让 mysqld 重新打开一个新文件 mysqladmin -uroot -p flush-logs # 或者在客户端里执行 FLUSH GENERAL LOGS; # 3. 压缩归档,确认无误后再删 gzip /data/mysql/logs/general_probe.log.bak

这一步之所以要先mv再flush,是因为直接删除会让 mysqld 继续往一个已经不存在的文件写入,直到它自己重新打开为止,期间的数据就丢了。改名再刷新,新文件由 mysqld 自己创建,属主和权限都是对的。

表格模式的处理要更小心。mysql.general_log用的是 CSV 引擎,清空之前必须先关掉日志,否则可能出现写入竞争或者清空失败:

SET GLOBAL general_log = OFF; TRUNCATE TABLE mysql.general_log;

清完之后如果还想继续抓,再开回去。注意表模式下即使关了日志,表里的历史数据也不会自动清,这是很多人以为“关了就没数据了”的误区。

如果你的环境长期需要轮转,可以配一份 logrotate 规则,但postrotate里执行刷新的那个用户必须是 mysqld 的运行用户,否则新文件的属主会变成 root,mysqld 写不进去,日志就静默断了。这一点我在两个不同的环境里都踩过,症状是“日志突然停在某个时间点不动了”,查半天才发现是权限问题。

3.4 日志分析:awk、pt-query-digest 与表查询

拿到日志之后,真正花时间的往往不是抓,而是从里面挖出想要的东西。这里分享几个我常用的路子,按复杂度递增。

最简单的是按账号或按来源 IP 过滤,grep加awk就够:

# 找出某个用户的全部动作 grep 'app_ro\[' /data/mysql/logs/general_probe.log | head -100 # 统计每个来源 IP 的指令条数(文件模式,按 TAB 分列) awk -F'\t' '{print $2}' general_probe.log | awk '{print $3}' | sort | uniq -c | sort -rn | head -20

注意文件模式的分隔符是制表符,但不同版本、不同输出格式下字段位置会有细微差别,先head看两行再写awk,别硬套。我自己就因为直接套了网上的命令,统计出来全是错位的字段,白高兴一场。

再往上一层是按 SQL 模板聚合,找出重复率最高的语句。这时候老牌的pt-query-digest就派上用场了,它支持把 general log 当输入解析,直接给出按模板归并的统计结果:

pt-query-digest --type=genlog /data/mysql/logs/general_probe.log > digest.txt

输出会按执行次数、总耗时(general log 没有真实耗时,这里是估算)排序,帮你快速看出哪类语句刷得最凶。这个工具对 general log 的支持需要指定类型,不指定它会按慢日志去解析,结果就很奇怪。

如果log_output是TABLE,那就直接用 SQL 查,但务必加上时间范围,别全表扫:

SELECT event_time, user_host, thread_id, command_type, argument FROM mysql.general_log WHERE event_time BETWEEN '2024-06-11 02:00:00' AND '2024-06-11 02:20:00' AND command_type = 'Query' ORDER BY event_time LIMIT 200;

这张表没有索引,上面这个查询本质上还是全表扫,所以只在数据量小或者已经清空重开的场景下用。数据量一大,查一次就得几十秒,还占着资源,不如老老实实写文件。

4. 性能代价到底有多大

4.1 写入放大与文件模式、表模式的差异

很多人问“开 general log 会影响性能吗”,标准答案是“会”,但更重要的是“影响在哪”。它带来的不是 CPU 飙高,而是日志写入路径上的串行化。MySQL 写 general log 和写二进制日志一样,需要持有日志锁,所有会话的日志记录在这一把锁上排队。文件模式下排队时间很短,因为就是一次顺序追加;表模式下每次写入要打开 CSV 文件、写入、关闭,临界区变长,排队的效应被放大。

具体到数字,我在测试环境里做过几组对比。单机、机械盘、500 QPS 的读请求为主、语句平均 80 字节:

输出模式TPS 变化平均响应时间变化备注
关闭基准基准对照组
FILE下降约 10%~20%上升 0.2~0.5 ms顺序写,基本可接受
TABLE下降约 40%~60%上升 2~8 msCSV 无索引,写放大明显

这组数字不严谨,硬件、语句长度、并发模型都会影响结果,但它说明的趋势是稳定的:表模式不适合任何有并发压力的场景。有些团队为了“方便用 SQL 查”选了表模式,结果排查还没开始,业务先报障了,得不偿失。

还有一个隐性的成本容易被忽略:日志文件变大之后,磁盘 IO 的压力不只是写入,还有文件系统本身的元数据操作;如果你把日志和数据放在同一个分区,数据页的刷盘也会被挤。条件允许的话,把general_log_file指到独立分区,能减少干扰。

4.2 生产环境的安全开启策略

既然必须用,就得有规矩。我自己的执行清单大致是这样:

  • 时间窗口控制在 10 到 30 分钟,写进变更单,写清楚起止时间和开启人。
  • 优先在只读库或者从库上开,避免影响主库写入。从库上开有额外好处:能抓到复制线程之外的应用查询,减少干扰。
  • 输出模式一律FILE,日志目录单独分区,提前确认剩余空间大于预估量的三倍。
  • 开启前先记录Questions基线,开启后再看一次,方便事后折算日志量。
  • 开启期间密切看磁盘和响应时间,一旦发现毛刺就立即关闭,不要抱着“再等两分钟”的侥幸。
  • 关闭后立刻改名归档,压缩,然后按需分析,别让大文件长期躺在数据目录里。

还有一条经验:如果是排查“某条语句是谁发的”,其实不需要开很久。先通过别的线索把时间窗口缩小到几分钟,比如从业务日志里找到报错的精确时间,然后只开那几分钟。我见过有人一开就是一整天,最后从几个 G 的日志里翻两行,纯属给自己找麻烦。

如果没有权限开全局开关,或者完全不能接受性能影响,还有替代路径,后面单独讲。

5. 常见问题速查与踩坑记录

5.1 开了却看不到 / 看不到全

这是最高频的问题,原因通常就那几个,我整理成一张速查表:

现象可能原因排查方法
文件存在但内容为空开关打开后没有新语句,或写的是另一个文件确认general_log为 ON,确认general_log_file实际路径
改了路径但还写老文件没执行日志刷新执行FLUSH GENERAL LOGS后再看
只有 Query 没有 Connect开关在连接建立之后才打开重新建立连接测试
预处理语句看不到参数协议差异,参数在 Prepare/Execute 中按 thread_id 查上下文
日志突然停在某时间点文件被移走但没刷新,或权限错误检查文件属主,执行刷新

其中“日志突然停了”这条最隐蔽。表现是general_log还是 ON,但文件不再增长。原因要么是文件被外部工具移走、mysqld 还握着老句柄,要么是新建的文件属主不对、写入失败。第二种情况下 mysqld 不一定报错,只是在错误日志里偶尔留一行,很容易漏看。养成习惯:轮转之后一定要tail一下新文件确认在写。

5.2 表格模式下的四个坑

选表模式的人不多,但踩坑的比例很高,集中说四个:

第一,表是 CSV 引擎,不能加索引,不能改引擎,想优化查询只能靠清空和分批查。第二,清空前必须先关日志,否则容易出问题。第三,TRUNCATE之后表空间不会自动收缩,文件还在那儿占着磁盘。第四,表里的数据是明文的,和其他业务表混在同一个实例里,权限管理不当时,有权限读这张表的人就能看到所有人的 SQL,包括带敏感参数的语句。

我个人的态度很明确:表模式只用于教学演示和单机临时排查,任何有并发、有合规要求的环境,都别用。

5.3 磁盘、权限与信息泄露

磁盘这块前面算过账,这里补充一个更极端的情况:如果日志分区真的写满了,影响的不只是日志,mysqld 可能连临时文件都创建不了,连接会报错,严重时实例不可用。所以开日志这个动作,本质上是在赌你的磁盘容量估算准不准。我的做法是给日志目录单独挂一个小分区,写满就写满,至少不牵连数据目录。

权限这块,除了前面说的轮转后属主问题,还有一个更值得警惕的点:general log 会把 SQL 原文完整记录下来,包括明文密码。执行CREATE USER ... IDENTIFIED BY 'xxx'、SET PASSWORD、某些应用初始化时拼接的连接串,都可能原样落盘。日志文件如果权限设置过宽,同机器上的其他账号就能读到。所以日志目录的权限建议收紧到仅 mysqld 运行用户可读,归档文件加密或者及时销毁。

这也是我一直强调“用完就关”的另一个原因:它不只是一个性能问题,还是一个数据安全问题。日志里躺着的东西,比你以为的多得多。

6. 什么时候别用它:替代路径对比

6.1 场景对照表

不是所有“想看语句”的需求都该用 general log。按场景选工具,效率差好几倍:

需求场景推荐方案理由
定位某条慢语句的来源慢查询日志 + 应用日志时间对齐开销小,长期可开
还原某个会话的完整行为general log(短时)唯一能全量记录的手段
观察高频语句分布performance_schema 语句摘要表常驻、聚合、开销可控
合规审计、防篡改专业审计插件有独立存储和完整性保障
排查锁等待与阻塞performance_schema 等待事件比日志更直接

这张表的核心意思是:general log 的不可替代性只体现在“全量还原会话轨迹”这一件事上。其他需求都有更轻的方案。

6.2 慢日志加零阈值的小技巧

如果只是想知道“这段时间都跑了哪些语句”,其实有个很取巧的做法:临时把long_query_time设成 0,让慢日志把所有语句都记下来。这样得到的是一份带执行耗时、扫描行数、返回行数的完整记录,信息密度比 general log 高得多,而且慢日志的写入路径经过长期优化,开销比 general log 小不少。

SET GLOBAL long_query_time = 0; SET GLOBAL slow_query_log = ON; -- 抓一段时间后恢复 SET GLOBAL slow_query_log = OFF; SET GLOBAL long_query_time = 1;

这个方案的局限在于:它只记语句,不记连接建立和断开,也不记Connect的来源信息;而且它记录的是执行完成的语句,被中断的、连接层就被拒绝的,都看不到。所以它适合“看语句分布”,不适合“追人”。

6.3 performance_schema 常驻观察

新版本上的performance_schema提供了一套常驻的语句观测能力,其中的语句摘要表按 SQL 模板聚合,记录执行次数、总延迟、扫描行数等指标,开销很小,可以一直开着。想看最近执行过的具体语句,还有按线程保存的历史记录表,能翻出每个会话最近若干条语句。

SELECT THREAD_ID, EVENT_NAME, SQL_TEXT FROM performance_schema.events_statements_history ORDER BY EVENT_ID DESC LIMIT 50;

这套东西的短板是容量有限,历史表只保留固定条数,会话一多就互相覆盖,做不到 general log 那种“全量落盘”。但作为日常观察手段,它比 general log 合适太多,属于“常备药”而不是“急救药”。

7. 我自己的几条使用原则

折腾这么多年,关于 general log 我最后沉淀下来的原则就几条,都是吃亏换来的。

第一,把它当探针,不当监控。任何“长期开着吧,反正也不差这点性能”的念头,最后都会以磁盘告警或者响应毛刺收场。开之前想清楚要抓什么,抓完立刻关,文件归档,流程闭环。

第二,永远先算容量再动手。用Questions估 QPS,乘上平均记录长度和预计时长,这个数字出来之后你自己就知道该开几分钟。我现在的习惯是预估量超过 2 GB 就拆成两段抓,而不是一口气开一小时。

第三,输出目标只选文件。表模式除了查询方便一无是处,而查询方便这件事,grep和awk做得更好,还不用给数据库添负担。

第四,轮转之后必须验证。改完名、刷完日志,tail一下新文件,确认有内容在写,确认属主正确。这一步花十秒钟,能省掉后面“为什么日志是空的”的半小时。

第五,日志文件按敏感数据处理。里面有账号、有来源 IP、有完整的 SQL 原文,可能还有明文口令。归档时压缩加密,用完及时销毁,别随手放在共享目录里。

这套东西没有多高深,难的是每次都照着做。真到了半夜被叫起来排查问题的时刻,你会庆幸自己平时把这些细节都捋顺了。