触发器不推荐用于业务操作时同步更新维度表,因其易引发死锁、阻塞主业务、掩盖一致性问题,且无法处理批量操作或历史补数;应改用定时任务+存储过程实现解耦、可控的upsert逻辑。

触发器不是报表生成的推荐路径,尤其不适合“在业务操作时同步更新维度表”这类场景。它容易引发死锁、阻塞主业务、掩盖数据一致性问题,且无法处理批量操作或历史补数。
SQL Server 触发器里调用 INSERT / UPDATE 维度表会卡住主事务
触发器运行在原事务上下文中,一旦你在 AFTER INSERT 里去写另一张报表维度表(比如 fact_hourly_summary),这个写入就和原始 INSERT 共享同一把锁。如果维度表上有索引、外键或触发器嵌套,极易触发锁升级甚至死锁。
- 现象:业务插入
sensor_data后响应变慢,sp_who2显示WAIT_TYPE = LCK_M_U或LCK_M_SCH_S - 原因:触发器中
UPDATE fact_hourly_summary SET max_temp = ... WHERE hour = ...尝试获取行锁,但该行正被其他并发写入持有 - 更糟的是:若维度表本身也有触发器,会触发嵌套调用,SQL Server 默认只允许嵌套 32 层,超限直接报错
Msg 217, Level 16
MySQL 的 BEFORE INSERT 触发器不能访问本表新数据做聚合
你没法在 BEFORE INSERT 里用 SELECT MAX(temp) FROM sensor_data WHERE hour = HOUR(NOW()) —— 因为新行还没落盘,SELECT 看不到它;而 AFTER INSERT 又不能改 NEW 的值,也就没法“修正”当前插入行的汇总字段。
- 典型错误写法:
IF NOT EXISTS (SELECT 1 FROM report_hour WHERE hour = HOUR(NEW.uploadTime)) THEN INSERT INTO report_hour ...—— 这在并发插入时大概率产生重复键冲突(ERROR 1062: Duplicate entry) - 真正安全的做法是用
INSERT ... ON DUPLICATE KEY UPDATE,但这必须提前建好唯一约束(如UNIQUE(hour)),而触发器本身不负责建约束 - MySQL 触发器也不能调用存储过程里的事务控制语句(
COMMIT/ROLLBACK),所以无法把维度更新包进独立事务里
替代方案:用定时任务 + 存储过程替代触发器
把“统计逻辑”从实时路径剥离,放到后台按需执行。这样既解耦、又可控,还能加重试和日志。
- SQL Server:用 SQL Server Agent 创建作业,调用存储过程
usp_UpdateHourlySummary,计划设为每 5 分钟跑一次,查WHERE uploadTime >= DATEADD(minute, -5, GETDATE()) - MySQL:启用
EVENT,注意先开权限:SET GLOBAL event_scheduler = ON;,再建事件event_update_hourly_report,频率设EVERY 5 MINUTE - 关键点:存储过程中用
MERGE(SQL Server)或INSERT ... ON DUPLICATE KEY UPDATE(MySQL)来 upsert 维度行,避免并发冲突 - 务必给
uploadTime字段加索引,否则每次扫描全表,定时任务越跑越慢
如果真要保留触发器,至少禁用递归和嵌套
仅限极简单场景(例如单行 insert、无并发、维度表极小),且必须显式关闭风险开关:
- SQL Server:
ALTER DATABASE YourDB SET RECURSIVE_TRIGGERS OFF;,并在触发器开头加IF TRIGGER_NESTLEVEL() > 1 RETURN; - MySQL:
SET SESSION sql_mode = 'STRICT_TRANS_TABLES';避免隐式转换导致误更新;触发器内所有SELECT必须带FOR UPDATE或明确指定隔离级别 - 永远不要在触发器里写跨库、跨服务器、调外部 API 或发邮件的逻辑 —— 这些操作失败会导致主事务回滚,业务不可用
真正难的不是写触发器,而是让多线程写入时维度表的数据始终可预测、可验证。触发器把校验时机绑死在 DML 瞬间,反而放大了不确定性。先想清楚“谁负责最终一致性”,再决定要不要用它。

















