要审计特定用户的INSERT/UPDATE/DELETE操作,推荐组合使用server_audit插件(Percona/MariaDB)、binlog解析(社区版8.0+)及触发器+init_connect方案,三者互补覆盖用户识别、语句记录与行级证据链。

general_log 不能满足要求——它不区分用户、不记录 IP、不结构化、性能开销大,且无法过滤“特定用户”。真要审计特定用户的 INSERT/UPDATE/DELETE,得绕过通用日志,用更精准的手段。
用 server_audit 插件按用户+事件类型过滤(Percona/MariaDB 推荐)
如果你用的是 Percona Server 或 MariaDB,server_audit 是最接近开箱即用的方案。它支持直接按用户名和 SQL 类型筛选,日志格式清晰,还能写入文件或 syslog。
- 确认插件已加载:
SHOW PLUGINS LIKE 'server_audit';,状态必须是ACTIVE - 启用并限定范围:
SET GLOBAL server_audit_logging = ON;,再设SET GLOBAL server_audit_events = 'QUERY_DML';(只捕获 DML,不含 SELECT) - 过滤特定用户:插件本身不支持正则或白名单,但日志里每行都含
user和host字段,后续可用grep 'user@host'或 ELK 提取 - 注意:
server_audit不记录 WHERE 条件内容,只记完整语句;若语句被参数化(如 PREPARE),实际值可能不可见
MySQL 社区版 8.0+ 只能靠 binlog + mysqlbinlog --base64-output=DECODE-ROWS -v 回溯
社区版没 audit_log 插件,binlog 是唯一带行级变更且性能可控的来源。但它不存原始登录用户,只存执行线程的 user@host——如果走代理(如 ProxySQL、中间件),这个字段可能被覆盖成代理账号。
- 确保
binlog_format = ROW,且log_bin = ON;否则 DML 无法还原具体行变更 - 解析时加
--base64-output=DECODE-ROWS -v才能看到### UPDATE ... ### WHERE @1=...这类可读内容 - 查特定用户操作:先从
SHOW BINLOG EVENTS或mysqlbinlog输出中提取user@host,再结合时间窗口过滤对应 binlog 文件 - 缺点明显:无法关联应用层真实操作人(比如所有请求都经统一账号连库),也不能捕获失败的 DELETE(binlog 只记成功事务)
触发器写审计表:唯一能保证“谁删了哪几行”的兜底方案
当插件不可用、binlog 用户信息失真、又必须锁定到具体用户行为时,只能在业务表上挂 BEFORE DELETE 等触发器,把 USER()、CONNECTION_ID()、INET_NTOA(INET_ATON(SUBSTRING_INDEX(USER(), '@', -1)))(粗略提 IP)和 WHERE 条件(需手动拼)全写进审计表。
- 不要用
AFTER触发器——万一事务回滚,审计日志却已落盘,造成不一致 -
USER()返回user@host,CURRENT_USER()返回授权账户,两者可能不同;审计建议用USER() - WHERE 条件无法自动获取,只能靠
SELECT COUNT(*) FROM OLD_TABLE WHERE ...预估影响行数,或干脆不记条件(安全起见) - 触发器有性能开销,高并发 DELETE 场景下可能成为瓶颈;别给非核心表加,优先保关键表(如
users、orders)
别忽略 init_connect + connection_id 关联线索
单靠触发器或 binlog 很难把操作链串起来。配合 init_connect 把每次连接的 USER() 和 CONNECTION_ID() 记进一张 connection_log 表,就能把触发器里的 CONNECTION_ID() 和真实用户对上号。
- 设置:
SET GLOBAL init_connect = "INSERT INTO dba.connection_log VALUES (CONNECTION_ID(), USER(), NOW());"; - 必须给所有普通用户授予该表的
INSERT权限,否则连接会失败(init_connect执行失败会导致连接中断) - 注意:root 用户默认跳过
init_connect,所以 DBA 直连行为不会被记录——这是设计使然,不是 bug
真正落地时,server_audit 或 binlog 解析负责“宏观操作流”,触发器 + init_connect 负责“微观证据链”。三者不互斥,但别指望单点方案解决所有问题。最容易被忽略的是:用户身份在代理层之后就不可信,而触发器里的 USER() 是最后一道可信锚点。


















