不能用触发器直接同步两张业务表——除非它们在同一个数据库、同一实例、且不涉及跨库、跨实例、双向写或高并发场景;否则大概率会卡死主业务、引发循环写入、或在主从复制中彻底失效。

SQL Server 同库同实例表同步最稳写法
只要目标表和源表都在同一个 SQL Server 实例的同一个数据库里,AFTER INSERT, UPDATE, DELETE 触发器是可行的。关键不是“能不能”,而是“怎么写才不翻车”:
-
SET NOCOUNT ON必须加,否则客户端可能因多结果集报错 - 区分操作类型:用
IF EXISTS(SELECT 1 FROM inserted) AND NOT EXISTS(SELECT 1 FROM deleted)判断 INSERT;用EXISTS(inserted) AND EXISTS(deleted)判断 UPDATE;仅EXISTS(deleted)判断 DELETE - UPDATE 语句必须显式 JOIN
inserted和目标表,不能只靠 WHERE 主键匹配——否则在批量更新时易漏行或误更新 - 避免在触发器里调用函数(如
GETDATE()多次)、子查询嵌套过深,或写日志表——这些都会拖慢主事务
MySQL 触发器同步的致命限制
MySQL 的触发器作用域被硬性限制在当前数据库内:INSERT INTO other_db.t2 会直接报错 ERROR 1146 (42S02),哪怕你有跨库权限也不行。更麻烦的是:
- ROW 格式 binlog 下,触发器写入的语句不会被复制到从库(因为它是“衍生写入”,非原始 DML)
-
ERROR 1442 (HY000)是高频报错,本质是 MySQL 禁止在触发器中修改正在被当前语句访问的表(包括隐式访问) - 即使绕过错误强行写入,主从延迟、GTID 跳跃、或从库启用
read_only=ON都会让同步静默失败
跨库/跨实例同步必须绕开触发器
一旦涉及不同数据库、不同服务器、或需要容错能力,触发器就不再是方案,而是风险源:
- SQL Server 用链接服务器 +
OPENQUERY是唯一勉强可行路径,但远程执行失败时@@ROWCOUNT永远为 0,你无法知道 insert 是否真成功;且必须设rpc out = true,否则直接拒绝执行 - PostgreSQL 的
dblink_exec()不保证原子性:本地事务回滚了,远程 SQL 可能已提交,得自己建幂等表+补偿逻辑 - MySQL 根本没提供任何跨库触发能力,所谓“同步”都是应用层监听
binlog(如 Canal、Maxwell)或用 FEDERATED 引擎(已移除)伪造出来的
真正该用什么替代触发器
生产环境里,没人靠触发器做业务表同步。靠谱路径只有两条:
- 用 CDC 工具(如 Debezium、Canal)捕获源表变更,经 Kafka 或 Pulsar 中转,由消费者服务写入目标表——所有异步、可重试、可监控、不阻塞主业务
- 数据库原生复制:SQL Server 的 Always On 可读副本、MySQL 的 GTID 复制、PostgreSQL 的 logical replication,都支持过滤表、映射字段、跳过特定操作
最容易被忽略的点是:你在单机上反复验证成功的那个触发器,在生产环境面对网络抖动、连接池耗尽、防火墙策略变更时,第一分钟就会让主表 INSERT 开始超时。它不是“不够好”,而是设计上就不该出现在这里。










