可行,但必须建表时声明主键为NOT NULL,并在BEFORE INSERT触发器中用IFNULL(NEW.id, UUID())判空赋值,禁用UUID_SHORT()和DEFAULT UUID()。

直接用 BEFORE INSERT 触发器生成 UUID 是可行的,但必须避开两个高频坑:主键字段没设 NOT NULL、触发器逻辑没判空。
建表时主键字段必须显式声明 NOT NULL
MySQL 不允许主键字段为 NULL,哪怕你打算用触发器填值。如果建表时漏写 NOT NULL,插入时会报错 ERROR 1048 (23000): Column 'id' cannot be null,且触发器根本不会执行。
- ✅ 正确写法:
CREATE TABLE users (id CHAR(36) NOT NULL PRIMARY KEY, name VARCHAR(50)); - ❌ 错误写法:
CREATE TABLE users (id CHAR(36) PRIMARY KEY, ...)(隐式允许 NULL,实际不允许) - 别用
DEFAULT UUID()—— MySQL 8.0.13 之前不支持函数作默认值,8.0.13+ 仅支持部分函数(UUID()仍不被允许)
触发器里必须用 IFNULL() 或 IS NULL 判空
否则业务代码显式传入 id(比如迁移旧数据、联调测试)会被触发器覆盖,导致意外覆盖或主键冲突。
- ✅ 推荐写法:
SET NEW.id = IFNULL(NEW.id, UUID()); - ✅ 等效写法:
IF NEW.id IS NULL THEN SET NEW.id = UUID(); END IF; - ❌ 危险写法:
SET NEW.id = UUID();(无条件覆盖) - 注意:
REPLACE(UUID(), '-', '')只在需要无横线格式时才加,但别忘了字段长度要对应(32 字符),否则截断
别误用 UUID_SHORT() 当主键
UUID_SHORT() 看似紧凑,但它依赖服务器时间戳 + server_id,在多节点 MySQL 集群或主从切换后极易重复,已引发过线上主键冲突事故。
- ✅ 主键场景只用
UUID()(标准 36 字符带横线)或REPLACE(UUID(), '-', '')(32 字符无横线) - ❌ 禁止:
SET NEW.id = UUID_SHORT();(尤其不能用于分库分表或高可用架构) -
UUID()是纯内存计算,无锁无 I/O,性能影响可忽略;但索引效率略低于整型,这是 UUID 本身代价,和触发器无关
验证触发器是否生效的最小操作
别靠可视化工具点“新增”来测——有些工具(如旧版 Navicat)会把空字符串 '' 当作非空值传入,绕过 IFNULL 判断。
- ✅ 手动测试命令:
INSERT INTO users (name) VALUES ('test');(不提 id 字段) - ✅ 补充测试:
INSERT INTO users (id, name) VALUES ('abc', 'test');(显式传 id,应保持原值) - 查触发器是否存在:
SHOW TRIGGERS LIKE 'users'; - 删错可快速回退:
DROP TRIGGER users_set_uuid;
真正容易被忽略的是:触发器对 LOAD DATA INFILE 和 INSERT ... SELECT 同样生效,但如果你用 ORM 框架批量插入并显式指定了 id 字段,而框架又自动补了 NULL 或空字符串,IFNULL 就成了最后一道防线——这层防御必须写死在触发器里,不能指望应用层过滤。


















