触发器不能替代唯一约束防重,仅能在INSERT/UPDATE/DELETE时基于NEW/OLD执行额外SQL;防重必须依赖主键或唯一索引,触发器只适合日志记录、库存更新等事后操作。

触发器本身不能直接“合并”数据,也不能替代唯一约束做防重;它只能在INSERT/UPDATE/DELETE发生时,基于NEW和OLD临时行执行额外SQL。真正防重靠的是主键或唯一索引,触发器只适合做“事后补救”或“条件性同步”——比如插入订单后自动填充日志、更新库存,但别指望它扛住高并发重复插入。
跨表数据同步:用AFTER INSERT + INSERT ... SELECT最稳妥
常见误区是想让触发器“把A表某字段拼进B表某字段”,实际应避免字符串拼接式合并。更合理的是结构化同步:主表新增后,按规则生成关联明细。
- 必须用
AFTER INSERT,因为NEW.id等值在BEFORE里可能未生成(尤其自增主键) - 同步逻辑要明确目标表字段映射,别依赖
SELECT *——列顺序错位会导致静默写错 - 如果目标表有唯一约束,同步语句里要加
ON DUPLICATE KEY UPDATE(MySQL)或MERGE(SQL Server/Oracle),否则触发器会因主键冲突直接报错中断事务
示例:订单插入后,自动向order_summary表写入统计行(避免应用层多次查sum):
DELIMITER $$
CREATE TRIGGER tr_order_after_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
INSERT INTO order_summary (order_id, total_amount, item_count)
VALUES (NEW.id, NEW.amount, 1)
ON DUPLICATE KEY UPDATE
total_amount = total_amount + NEW.amount,
item_count = item_count + 1;
END$$
DELIMITER ;
防重不是触发器的本职工作:唯一索引才是第一道防线
试图用触发器查重再ROLLBACK属于高危操作:它无法防止两个并发INSERT同时通过查重判断,结果都写入——典型的竞态条件。数据库层面唯一约束(UNIQUE KEY)由底层加锁保障原子性,触发器做不到。
- 所有业务上要求“不能重复”的字段组合,必须建
UNIQUE INDEX,哪怕只是(user_id, product_id)这种复合唯一 - 触发器里做查重只适用于“软规则”,比如“同一用户24小时内最多下单3次”,这种需查历史记录的逻辑才适合放触发器
- 若真要在触发器里拦截,必须搭配
SELECT ... FOR UPDATE显式加行锁,但会严重拖慢并发性能,不推荐
MySQL中触发器访问多库表:路径必须带库名,且用户要有跨库权限
跨库同步常被忽略权限和语法细节。触发器内写INSERT INTO other_db.table_name没问题,但以下两点极易踩坑:
- 不能用
USE other_db切换上下文——触发器里不允许该语句 - 目标库用户必须对
other_db有INSERT权限,仅对当前库授权不够 - 如果目标表有触发器,MySQL默认禁用嵌套触发器(
max_sp_recursion_depth=0),需显式开启,但强烈不建议——容易引发死循环
安全做法是只同步到同库的汇总表或日志表,跨库同步改用应用层消息队列或Binlog解析工具(如Canal)。
最易被忽略的一点:触发器里的SQL报错会回滚整个原始事务。这意味着一个INSERT INTO orders失败,不仅订单没插成,连带之前应用层已执行的其他DML也会被撤回——调试时务必检查错误日志里是否混着触发器抛出的SQLSTATE异常,而不是只盯着主SQL。

















