MySQL触发器中禁止使用事务控制语句(BEGIN/COMMIT/ROLLBACK),无法局部回滚或重试;必须用AFTER INSERT获取自增ID;同步需幂等、避免跨库;仅适用于同库轻量级强一致辅助写入。

触发器里不能用事务控制语句
MySQL触发器执行时自动处于父事务上下文中,BEGIN、COMMIT、ROLLBACK这类语句直接报错:ERROR 1305 (42000): SAVEPOINT does not exist 或更常见的 ERROR 1305 (42000): FUNCTION does not exist(其实是语法错误伪装)。这意味着你无法在触发器里“重试失败的同步”或“局部回滚而不影响主表”。
- 如果同步逻辑涉及远程调用(比如调用存储过程发 HTTP 请求),必须确保该操作幂等且失败不中断主事务
- 写入本地另一张表是安全的,但要注意:目标表若被其他事务锁住,会拖慢原表的 INSERT/UPDATE/DELETE
- 避免在
BEFORE触发器中修改NEW字段后再做复杂判断——容易绕晕自己,也难调试
INSERT AFTER 触发器最常用,但别忽略 NEW.id 可能为 NULL
当主表主键是自增但未显式指定值时,NEW.id 在 BEFORE INSERT 中为 NULL,到 AFTER INSERT 才可用。想同步带主键的数据,必须用 AFTER 类型。
-
AFTER INSERT是唯一能安全读取完整自增值的时机;BEFORE阶段连LAST_INSERT_ID()都不可靠 - 如果目标表也有自增主键,别直接复制
NEW.id—— 除非业务强要求 ID 一致,否则建议用 UUID 或时间戳+随机数生成独立标识 - 注意:若主表用
INSERT ... SELECT批量插入,触发器对每一行分别触发,性能敏感场景要预估好开销
UPDATE 触发器需明确比较字段变化,别无脑同步
很多同学写 AFTER UPDATE 时直接把整行写进日志表,结果只要更新时间戳字段就触发一次冗余同步。实际应只响应关键字段变更。
- 用
IF OLD.status != NEW.status THEN ... END IF;显式判断,而不是靠OLD.* != NEW.*全字段比对(NULL 比较会失效) - 字符串字段注意 collation 和空格处理:
TRIM(OLD.name) != TRIM(NEW.name)比直接比更稳妥 - 如果同步目标是 Elasticsearch 或 Kafka,频繁小更新不如攒批——但触发器本身不支持攒批,得靠下游消费端合并
触发器无法跨库可靠同步,别把它当 MQ 用
触发器本质是同步阻塞执行,一旦目标表写入失败(如磁盘满、权限不足、约束冲突),整个原始 DML 就失败。这不是“同步失败后重试”,而是“根本没成功”。
- 错误信息典型如:
ERROR 1205 (40001): Deadlock found when trying to get lock或ERROR 1062 (23000): Duplicate entry 'xxx' for key 'PRIMARY' - 跨库同步(比如写另一个 MySQL 实例)必须走 FEDERATED 引擎或外部代理,但 FEDERATED 已在 8.0 中移除,且网络超时会导致主事务卡死
- 真正需要解耦的场景,应该用 binlog + Canal/Debezium,触发器只适合轻量、同库、低频、强一致性要求的辅助写入
触发器的边界很清晰:它不是数据同步方案,而是主表变更时的一次确定性副作用。越想让它扛更多事,越容易在凌晨三点被报警叫醒。











