触发器仅支持行级数据变更记录,无法替代数据库结构版本控制;必须为INSERT/UPDATE/DELETE分别创建独立触发器,使用原生函数生成时间戳和操作人,历史表需建(op_time, source_id)索引,DDL变更需靠快照或迁移工具实现。

触发器能做行级变更记录,但不能替代数据库结构版本控制——这是两个完全不同的问题域。别指望一个 CREATE TRIGGER 同时解决“谁改了哪条数据”和“表加了哪个字段”。
行数据变更必须分三类触发器独立实现
MySQL 和 PostgreSQL 都不支持单触发器响应多种 DML 类型(即使语法允许,逻辑混杂也极易出错)。你得为每种操作建单独触发器:
-
orders_after_insert:只存NEW.*,op_type = 'I' -
orders_after_update:用OLD.*和NEW.*对比,建议全量落库而非只存变化字段(避免漏字段、查关联表失败) -
orders_after_delete:只能用OLD.*,且必须立刻写入历史表(行已物理删除)
常见错误是把 UPDATE 和 DELETE 合在一个触发器里,结果 DELETE 时因事务隔离查不到关联数据而静默失败。
时间戳和操作人必须用数据库原生函数
别让应用层传 updated_at 或 modified_by ——既增加接口负担,又可能被绕过或注入。正确做法:
- 时间戳统一用
NOW()(MySQL)或CURRENT_TIMESTAMP(PostgreSQL) - 操作人用
current_user(PostgreSQL)或SUBSTRING_INDEX(USER(), '@', 1)(MySQL 8.0+),注意后者返回的是连接用户名,不是业务工号 - 若需精确到业务用户(如工号),应在主表预留
last_modified_by字段,由应用显式赋值,触发器再抄过去
历史表索引和性能隐患必须提前处理
触发器是同步执行的,UPDATE 主表卡住,往往是因为历史表没索引或磁盘慢。关键动作只有两个:
- 历史表必须建
(op_time, source_id)联合索引——source_id是原表主键,不是自增 ID - 避免在触发器里做跨库 JOIN、子查询或调用存储过程,这些都可能因锁或事务隔离导致失败
大表上建触发器前,先压测单条 UPDATE 的耗时;如果 >50ms,说明 I/O 或索引有问题,得先调优历史表。
DDL 变更根本没法用触发器捕获
ALTER TABLE、DROP COLUMN、CREATE INDEX 这些 DDL 操作,所有主流数据库都不支持对应触发器。你写 CREATE TRIGGER ... BEFORE ALTER 会直接报语法错误。
想追踪结构变更,只有两条路:
- 定期快照:用
SELECT * FROM information_schema.columns(MySQL)或pg_attribute(PostgreSQL)导出定义,每天比对 diff - 流程管控:所有 DDL 必须走
V20240801__add_phone_to_users.sql这类带版本号的脚本,由Flyway或Liquibase执行并记录
试图用触发器兜底 DDL,等于在数据库底层硬塞一个不存在的钩子——它不在协议里,也不在引擎设计中。

















