触发器必须部署在变更源头的数据库中,且仅用于写入同步日志表而非直接更新目标表;状态映射需通过独立映射表实现,避免硬编码;最终同步由幂等异步服务完成。

触发器必须放在变更源头的数据库里
同步账号状态的前提是:只有一方系统能真正修改状态(比如主身份系统),另一方只读或仅做镜像。如果你在两个库都建触发器互相更新,大概率会死锁或无限循环。实际部署时,UPDATE 或 INSERT 发生在哪边,触发器就必须落在哪边的数据库中——通常是主用户库。
常见错误现象:ERROR 1442 (HY000): Can't update table 'users' in stored function/trigger because it is already used by statement which invoked this stored function/trigger。这是因为触发器试图修改当前 SQL 正在操作的同一张表。解决方法是避免在触发器里直接改原表,只调用外部服务或写入中间同步日志表。
- 用
AFTER UPDATE而非BEFORE UPDATE,避开行锁冲突 - 触发器内不要写
UPDATE users SET status = ...这类语句(目标表就是它自己) - 推荐方案:把变更写入一张
sync_queue表,由独立同步服务轮询消费
用 INSERT INTO ... SELECT + 唯一键避免重复插入
当主系统新增用户时,需确保对方系统也创建对应账号。但不能每次 INSERT 都盲目执行,否则对方系统已有该用户就会报 Duplicate entry 错误。最稳妥的做法是在目标系统用 INSERT ... SELECT 加唯一约束(如 user_id 或 email),靠数据库本身拒绝重复。
示例(MySQL):
INSERT INTO remote_users (id, email, status, updated_at) SELECT NEW.id, NEW.email, NEW.status, NOW() FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM remote_users WHERE id = NEW.id );
- 必须提前在
remote_users上建UNIQUE KEY (id),否则无法拦截重复 -
FROM DUAL是 MySQL 写法;PostgreSQL 用SELECT ...即可,无需FROM - 别用
INSERT IGNORE或ON DUPLICATE KEY UPDATE,它们掩盖问题且不便于审计
状态字段值要映射,不能硬编码字符串
两个系统的状态枚举值往往不一致:主系统用 'active'/'disabled',而下游系统可能是 1/0 或 'ENABLED'/'LOCKED'。触发器里如果写死 VALUES ('active'),一旦对方改状态定义就全崩。
正确做法是建一张映射表 status_mapping:
CREATE TABLE status_mapping (
source_status VARCHAR(20),
target_system VARCHAR(20),
target_status VARCHAR(20),
PRIMARY KEY (source_status, target_system)
);
INSERT INTO status_mapping VALUES
('enabled', 'legacy_sso', '1'),
('disabled', 'legacy_sso', '0');
然后在触发器里 JOIN 查询:
INSERT INTO remote_users (id, status) SELECT NEW.id, m.target_status FROM status_mapping m WHERE m.source_status = NEW.status AND m.target_system = 'legacy_sso';
- 映射逻辑从触发器剥离,改状态规则只需改表,不用动 DDL
- 如果某状态无映射,整个 INSERT 会失败(空结果集),这反而是好事——提醒你补配置
- 别在触发器里用
CASE WHEN NEW.status = 'enabled' THEN '1' ...,维护成本高且易漏
触发器无法替代幂等通信,最终得靠异步服务兜底
触发器本质是数据库事务内的一段逻辑,它快、轻量,但也极度受限:不能发 HTTP 请求,不能连外部数据库(MySQL 默认禁用),超时或失败不会重试。一旦远程系统不可用,触发器里的同步逻辑就卡死或静默失败。
所以真实生产环境里,触发器只做一件事:可靠地记下「这里发生了什么」。剩下的交给一个独立进程:
- 触发器只写
INSERT INTO sync_log (table_name, row_id, event_type, created_at) - 同步服务定时查
sync_log WHERE processed = 0,调用 API 更新远端,成功后标为已处理 - 同步服务必须实现幂等:同一
row_id+event_type多次调用不产生副作用
最容易被忽略的是时间窗口问题:主系统用户状态改了三次,触发器写了三条日志,但同步服务只处理最后一条,中间两次就丢了。所以日志表必须带版本号或 updated_at,服务消费时按时间排序,丢弃旧版本。











