必须用触发器而非unique约束的场景包括:跨表判断(如插入orders前查users.email)、多表动态计算(如更新inventory时比对credit_limit与订单总额)、超外键能力的级联删除(清空三张关联表)、依赖current_timestamp的动态规则(仅工作日9:00–18:00插入工单)。

优先用 UNIQUE 约束,除非你明确需要跨表判断、副作用操作或动态逻辑——触发器不是“更高级的约束”,而是兜底手段,且代价明显。
什么时候必须用触发器而不是 UNIQUE 约束
UNIQUE 约束只作用于本表字段,语法上不支持子查询或 JOIN。以下场景它完全无能为力:
- 插入
Orders行前,检查email是否已在Users表中存在(跨表) - 更新
inventory时,需实时比对customers.credit_limit和本次订单总额(多表 + 计算) - 删除用户时,要同时清空其在
user_logs、user_settings、user_notifications三张表中的记录(级联深度超外键能力) - 仅允许在工作日 9:00–18:00 插入新工单(依赖
CURRENT_TIMESTAMP动态判断)
UNIQUE 约束能解决但触发器容易翻车的点
很多开发者误以为“写个触发器校验唯一性更灵活”,结果引入死锁、性能下降和静默失败:
-
BEFORE INSERT触发器里用SELECT ... FOR UPDATE查重,高并发下极易死锁;而UNIQUE约束由引擎原生处理,无此风险 - 触发器内用
COUNT(*) > 0判断冲突,会触发全表扫描;正确做法是EXISTS+ 走索引 - 忘记在触发器里显式
THROW或RAISERROR,事务不回滚,数据状态错乱却无报错 - 触发器逻辑藏在数据库里,应用层看不到;
UNIQUE约束定义直接写在CREATE TABLE语句中,一目了然
跨表唯一性该怎么做才靠谱
真要强制跨表唯一,别硬刚触发器——先看设计层能否收敛:
- 把被引用字段(如
Users.email)设为UNIQUE,再让目标表(如Orders)建外键指向它——这是最标准、最高效的做法 - 若需组合唯一(如
tenant_id + email),在Users表上建多列UNIQUE约束,并让Orders.tenant_id外键关联 - SQL Server 2016+ 可用计算列 + 唯一索引:
ALTER TABLE Orders ADD email_normalized AS LOWER(email) PERSISTED,再建UNIQUE INDEX IX_Orders_email ON Orders(email_normalized) - MySQL 8.0+ 支持
CHECK约束后,90% 的校验已无需触发器;PostgreSQL 中也应优先用CHECK+ 外键组合
触发器不是“更强大”的替代品,而是当约束彻底失效时的补位工具。它的逻辑一旦写错,调试成本远高于改一行 ALTER TABLE ... ADD CONSTRAINT。真正难的从来不是怎么写触发器,而是判断“到底该不该写”。










