mysql自带general_log不适合审计delete操作,因其记录所有语句导致日志爆炸、无用户ip/程序名等关键元数据、无结构化字段供过滤,且开启后性能显著下降;真正审计需用percona/mariadb插件、binlog解析或触发器兜底。

为什么 MySQL 自带的 general_log 不适合审计 DELETE 操作
因为 general_log 记录所有语句(包括 SELECT、连接、SET 等),日志体积爆炸,且无法区分“谁在什么时间删了哪张表的哪些行”。它不记录执行用户 IP、客户端程序名,也没有结构化字段供后续过滤分析。更关键的是,开启后性能下降明显,生产环境基本不可用。
用 MySQL 8.0+ 的 audit log plugin 实现精准捕获
MySQL 官方 audit_log 插件(需企业版)或 Percona Server / MariaDB 的替代方案(如 server_audit)能按事件类型过滤。但注意:原生 MySQL 社区版 **不包含 audit_log 插件**,必须确认版本和发行版:
- Percona Server:启用
server_audit_logging=ON,设置server_audit_events='CONNECT,QUERY',再用正则匹配DELETE\s+FROM - MariaDB:加载
server_audit插件后,配置server_audit_events='QUERY_DML'即可捕获 DELETE/INSERT/UPDATE - MySQL 社区版 8.0+:只能靠
binlog+mysqlbinlog --base64-output=DECODE-ROWS -v回溯,但无法关联原始登录用户(只记录执行线程的user@host,且可能被代理层覆盖)
示例 Percona 配置片段(/etc/my.cnf):
plugin_load_add = server_audit.so server_audit_logging = ON server_audit_events = CONNECT,QUERY server_audit_file_path = /var/log/mysql/server_audit.log server_audit_file_rotate_now = ON
用触发器 + 日志表做应用层兜底审计(兼容所有版本)
当无法使用插件或 binlog 解析时,这是最可控的方式:为关键表添加 BEFORE DELETE 触发器,把操作者、时间、WHERE 条件(尽量)、影响行数写入独立审计表。缺点是不能拦截跨库或 DROP TABLE,且触发器本身有性能开销。
- 审计表必须用
InnoDB并加索引(如created_at,table_name),否则高并发下写入成瓶颈 - 触发器中用
USER()获取当前连接用户,用CONNECTION_ID()关联 processlist 查客户端 IP(需额外 JOIN) - 无法直接拿到原始 SQL 的 WHERE 条件,可用
ROW_COUNT()记录删除行数,配合应用层日志交叉验证
最小可行示例:
CREATE TABLE audit_delete_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, table_name VARCHAR(64), deleted_rows INT, op_user VARCHAR(128), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB;
DELIMITER $$
CREATE TRIGGER t_audit_user_delete BEFORE DELETE ON user_info
FOR EACH ROW
BEGIN
INSERT INTO audit_delete_log(table_name, deleted_rows, op_user)
VALUES ('user_info', ROW_COUNT(), USER());
END$$
DELIMITER ;
真正容易被忽略的点:权限隔离与日志防篡改
审计日志本身必须由独立账号写入,且该账号对审计表只有 INSERT 权限,对原业务表无任何权限。否则管理员或攻击者可直接 TRUNCATE audit_delete_log 或 DROP 它。另外,general_log 和 audit_log 文件若落在本地磁盘,需确保 MySQL 进程无 rm 权限,并通过 chown root:root + chmod 640 限制访问。线上环境建议将日志实时推送至远程 SIEM 系统,而非仅依赖本地文件。











