MySQL通用日志(General Log)是数据库审计中最容易被低估的工具。很多DBA习惯性关闭它,理由是性能开销大,但在面临数据篡改追溯、开发人员误操作排查或合规审计时,没有日志就等于蒙眼狂奔。开启通用日志本身不难,难的是如何在不拖垮生产库的前提下,拿到真正有价值的审计信息。
首先要纠正一个认知误区:通用日志记录的只是客户端发来的原始语句文本,它不记录执行时间、影响行数或资源消耗。如果你需要性能分析,应该找慢查询日志;但如果你想知道“谁在什么时间从哪台机器连上来执行了什么SQL”,通用日志是目前MySQL原生提供的最直接手段。企业版MySQL的审计插件功能更强,但大多数团队用的是社区版,通用日志就成了唯一的内建选项。
开启方式与输出目标的选择通用日志默认处于关闭状态,可以通过全局变量动态开启,无需重启实例。核心变量有三个:
general_log:控制日志开关,取值为ON或OFF。
general_log_file:指定日志文件的路径和名称,默认为主机名.log。
log_output:控制输出目标,可选值为FILE、TABLE或两者同时使用。
如果选择FILE,日志会写入服务器文件系统上的物理文件。如果选择TABLE,日志会写入mysql数据库下的general_log表,这个表使用的是CSV存储引擎。很多人觉得写表方便查询,直接用SELECT就能过滤分析,但这里埋着一个大坑:general_log表的数据文件会随着时间线性增长,而且CSV引擎不支持索引,在大批量写入时,对mysql数据库所在文件系统的IO压力会急剧上升。更危险的是,如果general_log表所在的磁盘空间被写满,MySQL实例可能直接挂掉,因为系统表空间无法继续写入。
基于线上环境的稳定性考虑,强烈建议将log_output设置为FILE,让日志落到独立挂载的数据盘上,与数据文件、binlog、redo log物理隔离。这样做有两个好处:一是日志写满不会影响数据库核心表空间,二是日志文件的顺序写入性能远好于CSV表的随机写入。
动态开启与零停机配置线上数据库不能随意重启,所以掌握动态调整参数的方法至关重要。以下命令可以在秒级完成通用日志的开启和目标切换:
SET GLOBAL general_log = 'ON'; SET GLOBAL log_output = 'FILE'; SET GLOBAL general_log_file = '/data/mysql_logs/general.log';
执行后立即生效,所有新建连接发出的SQL都会被记录下来。但要注意,已经存在的连接不会重新加载日志配置,如果某个长连接在开启前就已建立,它的SQL不会出现在通用日志中。因此,在排查特定会话问题时,需要确认连接时间点。
动态设置的参数在MySQL重启后会丢失。如果希望永久生效,需要在配置文件my.cnf中加入:
[mysqld] general_log = ON general_log_file = /data/mysql_logs/general.log log_output = FILE
不过生产环境不建议永久开启通用日志,因为性能损耗是持续性的。更合理的做法是临时开启、用完即关,或者通过脚本在业务低峰期定时采样。
性能损耗的真实量级与应对策略通用日志的性能开销主要来自两方面:一是每次SQL执行都要追加写日志文件,二是日志写入操作会持有全局互斥锁,高并发场景下锁竞争会拖慢所有线程。根据实际压测数据,在QPS超过5000的OLTP场景下,开启通用日志可能导致整体吞吐量下降15%到30%,P99延迟明显升高。这绝对不是可以忽略不计的开销。
降低性能冲击有几个实用手段。第一,确保general_log_file指向的磁盘是高速SSD,并且文件系统挂载时使用noatime选项,减少元数据更新开销。第二,操作系统层面调整日志文件的写入策略,比如将日志目录所在的磁盘调度算法设置为deadline或noop。第三,利用MySQL的日志刷新机制,适当增大innodb_flush_log_at_trx_commit的容忍度,但这只影响InnoDB日志,对通用日志的直接帮助有限。真正有效的办法是控制开启时长和范围,只在需要审计的时间窗口内打开。
日志内容的过滤与安全风险通用日志会原封不动地记录每一条SQL,包括INSERT、UPDATE中的明文数据。这意味着用户的手机号、身份证号、密码哈希甚至明文密码都可能直接落盘。如果日志文件被恶意读取或者备份泄露,这就是一起严重的数据安全事件。很多安全合规审计要求日志必须脱敏,但MySQL原生通用日志不支持任何过滤或脱敏功能。
应对这个问题的思路不能依赖MySQL本身,而要在日志输出之后做后处理。可以在日志目录上部署一个轻量级的日志采集代理,实时读取general.log文件,在采集端通过正则表达式匹配敏感字段并脱敏,然后再上传到集中式日志平台。例如匹配手机号的正则可以写为:
1[3-9]\d{9}
匹配后替换为“1380000”之类的掩码。身份证号、银行卡号同理。关键是要做到实时脱敏,避免原始日志在磁盘上停留过久。另一个思路是使用操作系统层面的命名管道,将日志输出重定向到一个处理程序,实现不落盘直接处理,但这个方案复杂度较高,需要定制开发。
日志轮转与空间管理通用日志文件没有内建的轮转机制,不像错误日志或慢查询日志那样可以自动切割。一旦开启,文件会持续增长直到磁盘写满。MySQL本身提供了一个FLUSH GENERAL LOGS命令,它的作用是关闭当前日志文件并重新打开一个新的文件,效果类似于日志轮转。但这个命令不会自动重命名旧文件,也不会删除过期文件,需要配合外部脚本使用。
一个成熟的做法是结合Linux的logrotate工具进行管理。在/etc/logrotate.d/目录下创建一个mysql-general-log配置文件,内容大致如下:
/data/mysql_logs/general.log {
daily
rotate 7
missingok
notifempty
compress
delaycompress
postrotate
mysql -uadmin -p'password' -e "FLUSH GENERAL LOGS;"
endscript
}
这个配置会每天轮转一次,保留7天的日志,旧日志自动压缩。postrotate中的FLUSH命令通知MySQL切换到新文件。需要注意两点:一是logrotate的执行用户必须有读取日志目录和向MySQL发送命令的权限;二是FLUSH命令本身也会被记录到通用日志中,形成一条多余的记录,分析时需要过滤掉。
审计信息的提取与关联分析通用日志的格式非常简单,每一行由时间戳、连接ID和SQL文本组成,用制表符分隔。例如:
2024-07-15T10:23:45.123456+08:00 18 Query SELECT * FROM orders WHERE user_id = 1001
时间戳精确到微秒,连接ID可以关联到processlist中的会话,Query表示这是一条普通查询。如果要追溯某个用户的操作轨迹,需要结合应用层的用户标识。因为MySQL层面只能看到数据库用户名,而现代应用通常使用连接池,所有请求共用一个数据库账号,单靠连接ID无法直接对应到具体的业务用户。
解决办法是在应用代码中植入审计标记。每次获取数据库连接时,先执行一条SET语句,将业务用户ID、请求TraceID等信息写入一个会话变量,例如:
SET @audit_user = 'user_12345'; SET @trace_id = 'abc-def-ghi';
这两条SET语句会被通用日志记录下来,后续同连接上执行的所有SQL就都可以通过连接ID和时间窗口与业务用户关联。在日志分析平台中,可以编写查询语句将同一连接ID下的所有SQL聚合在一起,还原出完整的用户操作序列。这对于排查数据异常变更、定位误操作来源非常有价值。
与Binlog的互补关系有些人认为有了Binlog就不需要通用日志,这是对两种日志定位的混淆。Binlog记录的是数据变更的逻辑事件,用于主从复制和增量恢复,它只包含修改了数据的语句或行变更,不记录SELECT查询。而通用日志记录所有语句,包括查询和失败的SQL。在安全审计场景中,攻击者往往先通过大量SELECT探测数据结构和敏感信息,这些操作在Binlog中毫无痕迹,但在通用日志中一览无余。
另外,Binlog中的SQL是经过服务器解析和重写后的形式,可能与客户端发送的原始语句有差异。通用日志则保留了客户端提交的原始文本,包括注释、格式和拼写错误,这对于还原操作者的真实意图更有帮助。两者的配合使用可以构建更完整的审计链路:通用日志负责记录“谁看了什么”,Binlog负责记录“谁改了什么”。
常见故障排查实例通用日志在故障排查中的实战价值很高。举一个真实场景:某电商系统每天凌晨会执行一批定时任务更新库存,某天发现部分商品的库存被异常清零。通过慢查询日志找不到任何线索,因为更新语句本身执行很快。开启通用日志后,在问题复现的时间窗口内捕获到了全部SQL。分析发现是一条不带WHERE条件的UPDATE语句,连接ID指向一个定时任务调度器的长连接。进一步排查代码,发现是某个分支逻辑在特定条件下拼接SQL时遗漏了过滤条件。如果没有通用日志,这条SQL永远不会被发现,因为应用日志只记录了任务开始和结束,没有打印完整SQL。
另一个场景是连接风暴排查。当MySQL的并发连接数突然飙升时,通过SHOW PROCESSLIST只能看到当前快照,无法知道这些连接是从什么时候开始、执行了什么语句。通用日志中的Connect和Quit事件可以精确还原每个连接的建立和断开时间,结合连接ID可以分析出哪些客户端存在频繁短连接、哪些SQL执行时间过长导致连接堆积。
最终建议与操作红线通用日志是一把双刃剑,用好了是审计利器,用不好就是性能杀手。总结几条操作红线:生产环境默认关闭,按需动态开启;输出目标坚决使用FILE而非TABLE;日志文件必须独立磁盘分区,设置容量告警;开启期间密切监控IO Util和CPU Wait指标;采集日志时必须实时脱敏;建立自动轮转和过期清理机制,避免磁盘写满。只要守住这些底线,通用日志就能在关键时刻帮你还原真相,而不是制造新的故障。
