general_log不适合安全审计,因其设计定位仅为轻量级操作记录,不区分用户、无IP、无执行结果、无轮转机制;开启后无日志通常因log_output=NONE、general_log_file路径不可写或SELinux/AppArmor拦截。

general_log 不适合做安全审计——这不是配置问题,而是设计定位问题。它记录所有语句(含 SELECT),不区分用户、不带客户端 IP、不标记执行结果、无轮转机制,开久了容易写满磁盘或拖慢实例。真要审计,得换方案。
为什么 SET GLOBAL general_log = ON 后看不到日志?
不是没生效,是三个环节卡住了:
-
log_output是'NONE':这是默认值,设了general_log = ON也白搭,必须显式执行SET GLOBAL log_output = 'FILE'或'TABLE' -
general_log_file路径不可写:MySQL 进程用户(通常是mysql)对父目录没写权限,或路径中某级目录不存在(general_log_file不会自动创建多级目录) - SELinux/AppArmor 拦截:Ubuntu/CentOS 上常见,临时验证可用
sudo setenforce 0或sudo aa-disable /usr/sbin/mysqld
确认状态的最小命令组合:
SET GLOBAL general_log = ON; SET GLOBAL log_output = 'FILE'; SET GLOBAL general_log_file = '/var/log/mysql/general.log'; SHOW VARIABLES LIKE 'general_log%';
执行完立刻 ls -l /var/log/mysql/ 看文件是否生成、属主是否为 mysql。
FILE 和 TABLE 两种输出方式的实际差异
选哪种,取决于你“查什么”和“查多久”:
-
log_output = 'FILE':适合实时tail -f监控,配合grep -E '(GRANT|REVOKE|INSERT|UPDATE)'快速抓高危操作;但无法条件聚合,轮转得靠logrotate外部管理 -
log_output = 'TABLE':日志存进mysql.general_log表(CSV 引擎),能用 SQL 过滤,比如:SELECT * FROM mysql.general_log WHERE argument LIKE '%grant%' ORDER BY event_time DESC LIMIT 10;;但注意:mysql.general_log不支持索引,数据量一过百万就卡;且TRUNCATE TABLE无效,只能DROP TABLE + CREATE TABLE重建
为什么 GRANT/REVOKE 在日志里找不到?
不是日志没开,而是语句根本没进 MySQL 解析器:
- 被代理(如 ProxySQL、MaxScale)改写或拦截,原始语句没发到 MySQL
- 连接池复用旧连接,没触发新连接日志(
general_log记连接,但不记连接复用) -
argument字段被截断:受max_allowed_packet限制,GRANT语句长了就只留开头;建议查时用LOWER(argument) LIKE '%grant%'宽松匹配
真正可用的安全审计替代方案
如果合规或事故回溯是刚需,general_log 只能临时顶一下。必须上专用插件:
- MySQL 企业版:启用
audit_log插件,支持 JSON 格式、按事件类型过滤(CONNECT/QUERY/TABLE)、IP/用户/时间戳/执行结果全字段记录 - 社区版常用替代:Percona Server 的
audit_log插件 或 MariaDB 的server_audit;加载方式类似:INSTALL PLUGIN server_audit SONAME 'server_audit.so';,关键配置项包括server_audit_logging、server_audit_events、server_audit_file_path - 插件日志独立于 error/general log,支持按大小/时间轮转(依赖版本),且启动时需在
my.cnf中配置plugin_load_add = server_audit.so,否则INSTALL PLUGIN重启即失效
最常被忽略的一点:所有审计类插件都要求 MySQL 启动时加载,INSTALL PLUGIN 只是临时注册,不写入配置文件等于白配。


















