SQL 触发器从入门到进阶:原理、时机、实战与避坑指南
SQL 触发器从入门到进阶:原理、时机、实战与避坑指南
为初学者准备的由浅入深教程:带你理解触发器的本质、触发时机(INSERT/UPDATE/DELETE、BEFORE/AFTER)、行级与语句级差异,配合 MySQL 与 PostgreSQL 的实战示例、最佳实践与避坑清单。
目录
- SQL 触发器从入门到进阶:原理、时机、实战与避坑指南
- 一、什么是触发器(Trigger)?
- 二、使用场景与不建议的场合
- 三、MySQL 触发器实战
- 1)审计日志:记录更新前后值
- 2)写入前校验(阻止非法写入)
- 3)自动维护汇总表
- 四、PostgreSQL 触发器实战
- 1)审计日志
- 2)写入前校验
- 3)维护汇总表(UPSERT)
- 五、最佳实践与工程化
- 六、常见坑与排查
- 七、触发器 vs. 其他机制
- 八、实践清单(建议)
- 彩蛋:梗版总结(轻松读法)
一、什么是触发器(Trigger)?
- 概念:触发器是在表上定义的“自动执行的程序”,当发生特定数据变更(INSERT/UPDATE/DELETE)时,被数据库引擎在指定时机(BEFORE/AFTER)自动调用。
- 价值:集中处理审计与日志、数据衍生与同步、复杂约束校验,减少应用层重复代码。
- 与存储过程/函数的区别:触发器是“被动触发”,不直接调用;过程/函数是“主动调用”。触发器适合对数据变更做副作用处理,函数更适合计算返回值。
二、使用场景与不建议的场合
适用:
- 审计与历史:自动记录变更人、变更时间、旧值/新值。
- 衍生字段与数据同步:根据业务规则自动计算派生列,或同步到汇总表。
- 严格约束:在写入前阻止不合法数据(如跨字段校验)。
不建议:
- 隐式业务逻辑过多,导致“写一行触发一片”,难排查与迁移。
- 跨库/跨服务耦合(触发器里访问外部系统)——维护与可靠性差。
- 高并发写入的大表里做重逻辑,易放大锁与性能问题。
三、MySQL 触发器实战
MySQL 触发器为行级触发器(每行触发一次),常见时机:BEFORE/AFTER INSERT/UPDATE/DELETE
。
1)审计日志:记录更新前后值
DELIMITER //
CREATE TRIGGER trg_users_update_audit
AFTER UPDATE ON users
FOR EACH ROW
BEGININSERT INTO audit_logs(entity, entity_id, action, old_value, new_value, actor_id, created_at)VALUES('users', NEW.id, 'update',JSON_OBJECT('name', OLD.name, 'email', OLD.email),JSON_OBJECT('name', NEW.name, 'email', NEW.email),CURRENT_USER(), NOW());
END //
DELIMITER ;
要点:
OLD
引用变更前的值,NEW
引用变更后的值(INSERT 无OLD
,DELETE 无NEW
)。- 审计表建议有归档策略,避免无限增长。
2)写入前校验(阻止非法写入)
DELIMITER //
CREATE TRIGGER trg_orders_before_insert
BEFORE INSERT ON orders
FOR EACH ROW
BEGINIF NEW.amount <= 0 THENSIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'amount must be positive';END IF;
END //
DELIMITER ;
3)自动维护汇总表
DELIMITER //
CREATE TRIGGER trg_orders_after_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGININSERT INTO daily_metrics(stat_date, gmv, orders)VALUES(DATE(NEW.paid_at), NEW.amount, 1)ON DUPLICATE KEY UPDATE gmv = gmv + VALUES(gmv), orders = orders + 1;
END //
DELIMITER ;
四、PostgreSQL 触发器实战
PG 的触发器通过“触发器 + 触发函数(Trigger Function)”实现;触发函数必须返回类型 trigger
。
1)审计日志
CREATE TABLE IF NOT EXISTS audit_logs(id BIGSERIAL PRIMARY KEY,entity TEXT,entity_id BIGINT,action TEXT,old_value JSONB,new_value JSONB,actor TEXT,created_at TIMESTAMPTZ DEFAULT now()
);CREATE OR REPLACE FUNCTION fn_users_update_audit()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGININSERT INTO audit_logs(entity, entity_id, action, old_value, new_value, actor)VALUES('users', NEW.id, 'update',jsonb_build_object('name', OLD.name, 'email', OLD.email),jsonb_build_object('name', NEW.name, 'email', NEW.email),SESSION_USER);RETURN NEW;
END;
$$;CREATE TRIGGER trg_users_update_audit
AFTER UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION fn_users_update_audit();
2)写入前校验
CREATE OR REPLACE FUNCTION fn_orders_before_insert()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGINIF NEW.amount <= 0 THENRAISE EXCEPTION 'amount must be positive';END IF;RETURN NEW;
END;
$$;CREATE TRIGGER trg_orders_before_insert
BEFORE INSERT ON orders
FOR EACH ROW EXECUTE FUNCTION fn_orders_before_insert();
3)维护汇总表(UPSERT)
CREATE OR REPLACE FUNCTION fn_orders_after_insert()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGININSERT INTO daily_metrics(stat_date, gmv, orders)VALUES(date(NEW.paid_at), NEW.amount, 1)ON CONFLICT (stat_date)DO UPDATE SET gmv = daily_metrics.gmv + EXCLUDED.gmv,orders = daily_metrics.orders + EXCLUDED.orders;RETURN NEW;
END;
$$;CREATE TRIGGER trg_orders_after_insert
AFTER INSERT ON orders
FOR EACH ROW EXECUTE FUNCTION fn_orders_after_insert();
五、最佳实践与工程化
- 可观测性:为关键触发器增加日志/计数器,定期审计执行频次与耗时。
- 幂等与可重放:设计便于重放的逻辑;配合作业重跑不产生重复副作用。
- 变更管理:触发器定义纳入迁移脚本;版本化与灰度发布,避免一次性影响全库写入。
- 性能:触发器内仅做必要逻辑,避免长事务;谨慎访问大表与外部资源。
- 锁与顺序:注意
BEFORE
与AFTER
的锁持有时机与影响;减少冲突热点。 - 安全:最小权限原则;敏感数据写审计与脱敏。
六、常见坑与排查
- 黑盒副作用:应用层“看不见”的逻辑导致行为难解释;需文档化与监控。
- 连环触发:一个触发器更新另一张表,又触发新的触发器,可能产生递归/循环;需限制路径或加防抖开关。
- 性能惊喜(反语):行级触发器在批量导入时放大成本;导数前考虑暂时禁用或改为批处理作业。
- 事务冲突:触发器中修改热点行,易产生锁等待与死锁;拆分写入路径或改写顺序。
- 不可预期顺序:多触发器在同一时机的执行顺序可能不可控;尽量合并或显式约定。
七、触发器 vs. 其他机制
- 约束(CHECK/FOREIGN KEY):更基础也更可依赖;能用约束解决的不要写触发器。
- 视图/物化视图:读侧重用;写侧逻辑放触发器或应用层。
- 事件/任务调度器:定时批处理 vs 即时响应变更,选择最契合的时机模型。
八、实践清单(建议)
- 为
users
表添加更新审计触发器,记录变更字段与操作者。 - 为
orders
表添加BEFORE INSERT
校验,拦截非正数金额。 - 为
daily_metrics
维护AFTER INSERT
触发器,实现 UPSERT 式汇总。 - 压测导入 10 万行订单数据,对比“开/关触发器”的耗时与锁等待。
彩蛋:梗版总结(轻松读法)
- 触发器像“自动门”:你一靠近(写数据),它自己“嘀”地开关(自动执行),不用喊;
BEFORE/AFTER
像“化妆前后对比照”,前置防事故,后置留证据;OLD/NEW
是“前任/现任”视角,八卦都写进审计里;- 连环触发像“多米诺骨牌”,看着好玩,真倒起来谁都别想下班;
- 能用约束解决的别上触发器,就像能用路口红绿灯,就别安排交警跳舞;
- 大批量导入时先别让“自动门”疯狂开合,改走“人工通道”(批处理)更省电。