mysql触发器用uuid()赋值主键报duplicate entry,根本原因是未判断new.id是否为空或应用层显式传入id;应加if new.id is null then set new.id = uuid(); end if;,且建表时id需not null。

MySQL触发器里用UUID()赋值主键,为什么有时会报Duplicate entry
根本原因是触发器没做空值判断,或应用层插入时显式传了id字段。比如你建表时定义了id VARCHAR(36) PRIMARY KEY,但INSERT语句写了INSERT INTO t (id, name) VALUES ('abc', 'x'),触发器仍会执行SET NEW.id = UUID(),导致覆盖后与已有值冲突。
实操建议:
- 触发器中必须加
IF NEW.id IS NULL THEN SET NEW.id = UUID(); END IF; - 建表时
id字段要设NOT NULL,否则DEFAULT不生效,且NEW.id可能为NULL但不触发判断 - 避免在
BEFORE UPDATE里也写同样逻辑——UPDATE时不该改主键,否则会破坏外键引用或唯一性
MySQL 8.0.13+ 用DEFAULT (UUID())比触发器更稳,但要注意括号
老教程常漏掉括号,写成DEFAULT UUID(),这在 MySQL 8.0.13+ 会直接报错:Invalid default value for 'id'。正确写法是带圆括号的函数调用表达式。
示例建表语句:
CREATE TABLE users ( id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()), name VARCHAR(50) NOT NULL );
注意点:
-
DEFAULT (UUID())只对 INSERT 时未指定id字段生效;若语句中显式写了id值,哪怕为NULL,也不会触发默认值 - 该语法仅支持字符串类型(
VARCHAR/CHAR),不支持BINARY(16);如需二进制存储,仍得靠触发器 +UUID_TO_BIN() - LOAD DATA INFILE 导入时,若数据文件不含
id列,DEFAULT会自动填充;但若文件含空字符串或\N,则可能存入空值而非UUID
想节省空间又保持可读性?用UUID_TO_BIN(UUID()) + 触发器
标准UUID()返回36字符字符串,占空间大、索引效率低。转成BINARY(16)能减半存储、提升B+树局部性,但直接存二进制串不可读。折中方案是在触发器里完成转换。
典型做法:
- 建表时定义
id BINARY(16) PRIMARY KEY - 触发器中写
SET NEW.id = UUID_TO_BIN(UUID()); - 查数据时用
SELECT BIN_TO_UUID(id) AS id, name FROM users还原可读格式
注意陷阱:
-
UUID_TO_BIN()默认按 RFC 4122 标准重排字节序(把时间戳段移到前面),提升索引顺序性;但如果你用UUID_TO_BIN(UUID(), 1)禁用重排,就失去这个优势 - MySQL 5.7 不支持
UUID_TO_BIN,必须升级到 8.0+;低版本只能存字符串或自己写函数模拟 -
BIN_TO_UUID()返回带短横线的字符串,若应用层依赖无横线格式(如replace(uuid(), '-', '')),需额外处理
触发器生成UUID时,LOAD DATA和批量INSERT都适用,但有边界
BEFORE INSERT触发器对所有插入途径生效,包括LOAD DATA INFILE、INSERT ... SELECT、客户端驱动批量插入等。但它无法干预REPLACE INTO或INSERT IGNORE中的“忽略”逻辑——如果主键冲突,整行被跳过,触发器根本不会运行。
关键提醒:
- 触发器内不能调用存储过程或函数返回结果集,否则
LOAD DATA会失败 - 高并发下,多个连接同时触发
UUID()没问题(它是纯函数,无状态),但若你在触发器里加了SELECT ... FOR UPDATE之类锁操作,就会拖慢甚至死锁 - 真正容易被忽略的是:触发器逻辑在事务内执行,但
UUID()每次调用都生成新值——这意味着同一事务中多次插入,得到的UUID必然不同,无法靠“复用一个UUID”实现业务上的聚合标识











