预聚合表是将预计算结果持久化存储以跳过实时计算,核心是按业务统计口径(如用户+日期粒度)存聚合值而非原始数据,需严格对齐时间范围、字段类型与空值逻辑,并通过定时任务维护。

预聚合表不是“加个索引就能快”,而是把算好的结果存下来,让查询直接读,跳过实时计算。
预聚合表该存什么数据
存的是业务真正需要的统计口径,不是原始记录。比如报表要“每个用户近30天订单数+总金额”,就建一张 user_daily_summary 表,字段为 user_id、stat_date、order_count、total_amount,而不是照搬订单明细表结构。
- 粒度必须明确:是按天?按用户?按商品类目?粒度越粗,表越小,但下钻能力越弱
- 时效性要匹配业务:T+1 聚合足够用,就别强求实时;若需小时级,就得配定时任务每小时跑一次
- 避免冗余字段:不参与查询或过滤的列(如订单备注、地址)一律不进预聚合表
- 主键设计要防重复:推荐用
(user_id, stat_date)作联合主键,防止同一用户同一天被多次写入
如何保证预聚合结果不漏、不错
最常出问题的是 LEFT JOIN 后聚合缺失行、NULL 值未补零、WHERE 条件范围不一致。
- 原始表有 1000 个用户,但某天只有 200 人下单 → 预聚合子查询若只写
WHERE status = 'paid',结果就只剩 200 行 → 主表 JOIN 时另外 800 行直接消失 → 必须用LEFT JOIN+COALESCE(os.order_count, 0) - 时间范围要严格对齐:主表查
created_at >= '2026-07-01',预聚合 SQL 里也得用同样条件,不能写成created_at > '2026-06-30'(边界值可能差一秒) - 关联字段类型必须一致:
orders.user_id是BIGINT,预聚合表的user_id也得是BIGINT,否则索引失效,JOIN 变全表扫描 - 空值逻辑要显式声明:状态字段含
NULL(如待支付),而聚合只写了WHERE status = 'paid',就会漏掉这部分 → 要么在 WHERE 中补OR status IS NULL,要么在业务层确认是否需要统计
MySQL 里怎么建和更新预聚合表
MySQL 没有原生物化视图,靠 CREATE TABLE ... AS SELECT + 定时任务最稳,比触发器或应用层双写更可控。
- 建表语句示例:
CREATE TABLE user_30d_summary AS SELECT user_id, COUNT(*) AS order_count, SUM(amount) AS total_amount FROM orders WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY user_id; - 更新用
REPLACE INTO或TRUNCATE + INSERT:避免用UPDATE,因为聚合结果是重算的,不是增量修 - 定时任务建议用系统 cron 调
mysql -e "REPLACE INTO user_30d_summary ...",不要依赖应用服务器调度,以防服务重启中断 - 加索引别忘:至少给
user_id加主键,如果常按日期查,再加(stat_date, user_id)复合索引
什么时候不该用预聚合表
不是所有慢查询都适合这条路——它换来了查询快,代价是存储、延迟和维护复杂度。
- 数据量小(
- 维度组合爆炸:用户 × 商品 × 时间 × 渠道 → 预聚合表行数可能超原始表,反而拖慢写入和存储
- 业务指标天天变:今天要“下单转化率”,明天要“加购到支付时长”,预聚合表很快变成负债
- 要求秒级实时:预聚合本质是异步,哪怕每分钟刷一次,也做不到真正实时
真正的难点不在建表,而在定义清楚“哪些维度必须固化、哪些条件永远不变、谁负责核对每日数据一致性”。一旦这些没对齐,表建得再快,查出来的数也是错的。


















