可靠方式只有触发器或应用层双写;数据库原生不提供DELETE自动记日志功能,必须主动干预执行路径。

DELETE操作触发审计日志的可靠方式只有触发器或应用层双写
数据库原生不提供“DELETE自动记日志”功能。想在DELETE执行时同步落库审计记录,必须主动干预执行路径。触发器最直接,但有隐式执行、调试困难、事务耦合紧等风险;应用层双写(先插日志再删主表)更可控,前提是业务逻辑能统一收口。
常见错误是依赖应用日志文件或异步消息——它们不保证与DELETE原子性一致,数据库回滚时日志已发出去,造成状态错乱。
- PostgreSQL推荐用
AFTER DELETE FOR EACH ROW触发器,读取OLD.*插入审计表 - MySQL 8.0+可用
BEFORE DELETE触发器,但注意OLD不可修改,仅可读 - SQL Server建议用
INSTEAD OF DELETE,把删除逻辑和日志写入封装进同一事务 - 避免在触发器里调用外部HTTP或写文件,失败会导致主操作失败
CDC捕获DELETE事件的关键是解析binlog/redo log而非监听SQL
应用层双写或触发器只能解决“我主动删”的场景,但无法捕获DBA直连执行的DELETE、误操作或跨库级工具(如pt-archiver)引发的变更。这时必须走CDC:从数据库底层日志中提取变更流。
不同数据库暴露变更的方式差异大,不能统一用“开启CDC”一句话带过:
- MySQL依赖
binlog_format=ROW+binlog_row_image=FULL,否则DELETE只记主键,丢失被删行完整快照 - PostgreSQL需启用
logical_replication=on,配合pgoutput或wal2json插件解析DELETE的oldtuple字段 - SQL Server开启
CHANGE TRACKING不记录DELETE详情,必须用CHANGE DATA CAPTURE(需企业版)并查cdc.fn_cdc_get_all_changes_*函数 - Oracle需配置
ARCHIVELOG模式 +supplemental logging,否则DELETE在redo中无足够信息还原行内容
审计日志表设计必须包含原始DELETE的上下文信息
只存“谁删了哪条ID”远远不够。真实排障需要知道:是不是批量删?WHERE条件是什么?来自哪个应用连接?事务是否跨多个表?
审计表至少应含以下字段,否则CDC捕获到的DELETE事件会失去可追溯性:
-
op_type(值为'DELETE',便于过滤) -
table_name(被删的主表名) -
pk_values(JSON或序列化字符串,存被删行主键值,如{"id": 123}) -
old_values(完整旧行内容,由CDC提供,触发器需显式拼接) -
event_time(用NOW()或CURRENT_TIMESTAMP,别用应用传的时间) -
tx_id(MySQL的GTID、PG的xid、SQL Server的__$start_lsn) -
client_info(触发器里取current_client(),或应用层透传application_name)
DELETE审计与CDC并存时最容易忽略事务边界和时序问题
当同时使用触发器写审计表 + 另起进程拉CDC流,会出现两条日志时间戳接近但内容不一致的情况。比如触发器写的old_values是删前快照,而CDC解析出的DELETE事件可能因binlog刷盘延迟晚到几毫秒——如果下游按时间排序合并,会误判成两次独立操作。
更隐蔽的问题是事务隔离:触发器内读OLD是当前事务快照,但CDC读的是已提交事务的log,若中间有其他事务修改了同一行又回滚,触发器看到的OLD可能和CDC最终输出不一致。
真正要落地,得选一条路走到底:要么全靠触发器(适合简单审计),要么全靠CDC(适合多源、强一致性要求),不要混合。混合方案里,唯一安全的做法是让CDC作为唯一真相源,触发器只做轻量标记(如打个audit_pending flag),后续由CDC消费者补全详情。

















