MySQL 5.7 中增量聚合需靠触发器+中间表手动实现,关键约束包括:INSERT仅用NEW、DELETE仅用OLD、UPDATE需同步处理新旧值,中间表须有覆盖分组字段的唯一索引,且避免在触发器中调用未声明READS SQL DATA的自定义函数。

MySQL 5.7 没有原生物化视图,增量聚合只能靠触发器+中间表手动实现;PostgreSQL 可用 REFRESH MATERIALIZED VIEW CONCURRENTLY 做近似增量,但本质仍是全量重算;Oracle 才真正支持基于日志的 FAST REFRESH。别指望写一条 SQL 就自动“增量物化”——数据库能力边界必须先划清。
MySQL 中用触发器做增量聚合的关键约束
触发器不是万能胶,它只在基表 DML 时被动响应,且极易因设计疏漏导致数据错乱:
-
NEW和OLD的使用必须严格匹配操作类型:INSERT 只有NEW,DELETE 只有OLD,UPDATE 两者都有;混用会引发空值或重复计数 - 中间表的主键/唯一索引必须覆盖所有分组字段(如
(customer_id, order_date)),否则ON DUPLICATE KEY UPDATE无法定位行,更新变成插入 - 避免在触发器中调用自定义函数,除非显式声明
READS SQL DATA,否则在binlog_format=STATEMENT下可能复制失败 - UPDATE 触发器要同时处理旧值减、新值加:比如金额变更,得先
total_amount = total_amount - OLD.amount,再+ NEW.amount,漏掉前者就偏高
PostgreSQL 物化视图的“伪增量”刷新陷阱
REFRESH MATERIALIZED VIEW CONCURRENTLY 看似聪明,实则依赖唯一索引做行级比对,不是真增量:
- 必须提前建唯一索引,字段需覆盖所有 JOIN 条件和业务主键,例如多表关联汇总时,索引得是
(user_id, product_id)而非仅user_id - 刷新期间若源表有并发 UPDATE,可能因索引冲突报错
SQLSTATE '55000',需捕获并退化为普通刷新 - 没有变更日志机制,每次
CONCURRENTLY都要全扫源表 + 全扫物化视图做 diff,亿级表下耗时未必比全量少 - 不支持 WHERE 过滤或子查询,复杂聚合必须拆成多层物化视图,否则刷新失败
Oracle FAST REFRESH 的硬性前提
想用 FAST REFRESH ON COMMIT 实现毫秒级同步,缺一不可:
- 基表必须有
PRIMARY KEY或ROWID,且不能是多表 JOIN 无主键关联 - 必须先建物化视图日志:
CREATE MATERIALIZED VIEW LOG ON orders WITH PRIMARY KEY, ROWID, SEQUENCE (order_amount) INCLUDING NEW VALUES - SELECT 列禁用
SYSDATE、ROWNUM、窗口函数、非确定性函数;含子查询需满足复杂性限制(如不能相关子查询) - 常见报错
ORA-12052就是上述任一条件不满足,此时自动降级为 COMPLETE 刷新,延迟陡增
高频更新场景下触发器的实际开销
每条 INSERT 都触发一次 UPDATE,看似轻量,但在高并发写入时会暴露瓶颈:
- 单次触发器执行若含
UPDATE ... WHERE无索引字段,会全表扫描,把 QPS 从 5000 压到 200 - 多个触发器嵌套(如 A 表 INSERT 触发 B 表 UPDATE,B 表 UPDATE 又触发 C 表)易引发死锁或递归超限
- 事务内批量 INSERT 1000 行,会触发 1000 次独立 UPDATE,不如事后用
INSERT INTO ... SELECT GROUP BY一次性聚合 - 真正可控的折中方案:用定时任务(如每分钟)拉取
orders表中updated_at > (SELECT MAX(updated_at) FROM mv_sync_log)的增量,再批量合并到汇总表
触发器和物化视图都不是“设好就不管”的黑盒;它们的数据一致性完全依赖你对基表变更逻辑的理解深度——漏掉一个 DELETE 触发器,或忘了给物化视图加唯一索引,几小时后查出来的总数就已不可信。


















