不能,BEFORE UPDATE触发器本身不自动保存历史版本,必须显式执行INSERT INTO archive_table SELECT OLD.*才能归档旧记录;否则仅空执行,历史数据丢失。

BEFORE UPDATE触发器真能自动保存历史版本?
不能直接靠它“归档”——BEFORE UPDATE 触发器本身不保存旧值到另一张表,它只在更新发生前执行逻辑。你必须显式写 INSERT INTO archive_table ... SELECT OLD.* 才能把旧记录存下来。漏掉这一步,就只是空跑一次触发器,历史数据照样丢。
触发器里怎么安全地插入旧记录?关键三点
很多线上事故源于触发器内操作没兜住异常或锁冲突。以下不是建议,是必须检查的底线:
-
archive_table必须和原表在同一个数据库、同一实例内;跨库INSERT在BEFORE UPDATE中可能失败(尤其开启binlog_format=ROW时) - 不要在触发器里调用存储过程或复杂函数——它们可能隐式开启事务或加锁,导致主表更新被阻塞
-
OLD.*只在BEFORE UPDATE和BEFORE DELETE中有效,AFTER触发器里已不可用;想存变更前快照,只能用BEFORE
为什么归档表主键不能直接用原表主键?
因为同一条记录可能被反复更新,每次都会生成一条旧记录。如果 archive_table 主键仍设为 id(原表主键),第二次更新就会因主键冲突报错:Duplicate entry '123' for key 'PRIMARY'。正确做法是:
- 归档表去掉
PRIMARY KEY,改用自增archive_id INT PRIMARY KEY AUTO_INCREMENT - 保留原
id字段作为普通索引(方便按源记录查历史) - 加上
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP记录归档时间
示例建表语句:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
CREATE TABLE user_archive ( archive_id INT PRIMARY KEY AUTO_INCREMENT, id INT NOT NULL, name VARCHAR(50), email VARCHAR(100), updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_source_id (id) ) ENGINE=InnoDB;
触发器性能和锁风险比你想的更实在
每次 UPDATE 都会额外多一次 INSERT,且该 INSERT 和原 UPDATE 在同一个事务里。这意味着:
- 原表行锁会延长持有时间,高并发更新场景下容易堆积锁等待
- 归档表写入变成本地热点,尤其当归档频率远高于查询频率时,
archive_table的auto_increment争用会明显 - 无法控制单次归档的数据量——哪怕只改一个字段,也得存一整行;对大文本字段(如
TEXT)极不友好
真正需要版本归档的业务(如合同、工单),建议改用应用层显式插入历史表 + 原表仅存最新态,而不是依赖触发器硬扛。










