应避免在触发器中用select max(id)获取主键,而应在应用层或id中心统一生成全局唯一id(如snowflake、ulid),分库分表下触发器无法跨库同步主键,必须弃用。

触发器里怎么让新插入的记录自动获得另一个表的主键值
直接在 INSERT 触发器中查目标表最新主键(比如 SELECT MAX(id) FROM other_table)不可靠:并发插入时会撞车,也可能被事务隔离级别干扰。真正稳妥的做法是用 LAST_INSERT_ID()(MySQL)或 RETURNING 子句(PostgreSQL),但前提是主键由当前语句生成且能捕获。
实操建议:
- MySQL 下,若目标表主键是自增,且你刚执行过
INSERT INTO other_table (...) VALUES (...),可在同一事务中用SELECT LAST_INSERT_ID()获取它,再赋给当前表字段 - PostgreSQL 下,直接在插入语句末尾加
RETURNING id,并在触发器中用NEW.other_id := (INSERT INTO other_table (...) RETURNING id)(需用BEFORE INSERT+EXECUTE FUNCTION或过程式逻辑) - 避免在触发器里做跨表
SELECT ... FOR UPDATE来“抢”主键,锁粒度大、易死锁
用触发器生成并写入 UUID 到本表主键字段,为什么总报错 “Duplicate entry”
常见错误是把 UUID()(MySQL)或 gen_random_uuid()(PostgreSQL + pgcrypto)直接写进 INSERT 语句的 VALUES,又在触发器里重复生成一次,导致冲突;或者触发器没区分 INSERT 和 UPDATE,UPDATE 时又覆盖了已存在的 UUID。
实操建议:
- MySQL:在
BEFORE INSERT触发器中只对NEW.id IS NULL的情况赋值NEW.id := UUID(),不碰UPDATE - PostgreSQL:定义主键字段为
id UUID DEFAULT gen_random_uuid(),再配合BEFORE INSERT ON ... FOR EACH ROW WHEN (NEW.id IS NULL)触发器兜底 - 别用
uuid_short()当主键——它依赖服务器时间+server_id,集群环境下不唯一
级联插入时触发器如何避免无限递归
比如 A 表插入触发 B 表插入,B 表插入又触发 A 表插入,循环就来了。MySQL 默认允许递归(max_sp_recursion_depth 控制),PostgreSQL 则默认禁止,但显式调用仍可能绕过。
实操建议:
- MySQL:在触发器开头加判断,如
IF @disable_cascade_insert THEN LEAVE proc_label; END IF;,并在外层 SQL 执行前设SET @disable_cascade_insert = 1 - PostgreSQL:用
TG_TABLE_NAME和上下文标记(如current_setting('app.cascade_level', true))控制嵌套深度 - 更干净的做法是放弃“纯触发器级联”,改用存储过程封装整个插入流程,把主键/UUID 生成、多表写入、错误回滚全控在一层
触发器同步主键 + UUID 的组合方案在分库分表下还行得通吗
不行。一旦表被水平拆分(比如按 user_id 分到不同实例),触发器只能操作本库本表,跨库取主键或生成全局 UUID 都会失败。ShardingSphere、MyCat 等中间件也不解析触发器逻辑。
实操建议:
- 分库场景下,彻底弃用触发器做主键同步,改用应用层统一生成
ULID或Twitter SnowflakeID,并作为所有关联表的外键来源 - 如果必须保留触发器结构,至少把 UUID 生成逻辑抽成独立函数(如 MySQL 的
FUNCTION get_global_id()),未来可替换成分布式 ID 服务调用 - 测试阶段务必在分片环境跑一遍,光看单库行为会漏掉最致命的跨库空指针和主键缺失
触发器不是银弹,尤其当主键要跨表、跨库、还要带业务语义时,越早把生成逻辑提到应用层或 ID 中心,后面踩的坑越少。










