一、问题场景:Dashboard 为什么这么慢?
你有没有遇到过这样的场景:一个 10 万行的订单表,每次打开 Dashboard 都要等好几秒才能看到数据?
很多数据分析师的做法是:加大机器配置、优化索引、或者干脆把查询拆成多个小请求。但这些都是在"治标"。
想象一下,如果你的 Dashboard 每天都要展示"按日期、按品类、按地区"的销售额汇总,而你每次都从 10 万条原始订单记录里重新计算 SUM 和 COUNT——这就像每次做饭都重新种菜一样浪费。
预聚合表的思路很简单:把高频查询的聚合结果提前算好,存成一张小表,后续查询直接读这张表。在 DuckDB 中,这只需要一条 CREATE TABLE ... AS SELECT ... GROUP BY 语句。
二、核心原理:用空间换时间
预聚合的本质是计算结果的缓存。当你反复执行相同的聚合查询(比如每天查看销售汇总),与其每次都从头扫描原始数据,不如将聚合结果持久化存储。
DuckDB 的列式存储引擎使得预聚合表的构建和查询都极其高效:
- 构建阶段:
GROUP BY聚合将 10 万行压缩到几千行,数据量减少两个数量级 - 查询阶段:小表的扫描成本远低于大表,即使加上窗口函数也更快
- 存储成本:预聚合表通常只有原始表的 1%-5%,磁盘开销极小
三、实战:从原始订单到预聚合表
3.1 创建预聚合表
假设你有一张 10 万条订单记录的 Parquet 文件,包含 order_id、order_date、category、region、amount 五个字段。
-- 将原始数据加载到内存表(如果源数据是 Parquet)
CREATE TABLE orders AS SELECT * FROM '/path/to/orders.parquet';
-- 创建日维度预聚合表:每天、每品类、每地区的汇总
CREATE TABLE daily_revenue AS
SELECT
order_date,
category,
region,
SUM(amount) AS revenue,
COUNT(*) AS orders,
AVG(amount) AS avg_order_value
FROM orders
GROUP BY 1, 2, 3;
这条 SQL 执行完后,daily_revenue 表的大小只有原始表的几百分之一(从 10 万行变成了几千行),但包含了 Dashboard 需要的全部汇总数据。
3.2 Dashboard 查询直接读预聚合表
-- 查询某天某品类的销售数据
SELECT * FROM daily_revenue
WHERE order_date = '2024-06-15'
AND category = 'Electronics';
-- 查询 Top 10 品类
SELECT category, SUM(revenue) AS total_revenue, SUM(orders) AS total_orders
FROM daily_revenue
GROUP BY 1
ORDER BY 2 DESC
LIMIT 10;
3.3 窗口函数在预聚合表上的应用
预聚合表还能加速窗口函数查询。以滚动 7 天均值为例:
-- 预聚合后:直接对汇总表应用窗口函数
SELECT order_date,
daily_rev,
AVG(daily_rev) OVER (
ORDER BY order_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS ma7
FROM daily_totals;
由于 daily_totals 表只有几百到几千行(取决于日期范围),窗口函数的计算成本极低。
四、实测性能对比
我用 10 万条订单数据做了真实测试,结果令人印象深刻:
场景一:每日 Dashboard 查询(50 次循环)
- 直接从 Parquet 文件查询:0.2644 秒
- 从预聚合表查询:0.0386 秒
- 提速 6.86 倍 ⚡
场景二:滚动 7 天均值(窗口函数)
- 原始表 + 窗口函数:0.1224 秒
- 预聚合表 + 窗口函数:0.0528 秒
- 提速 2.32 倍 ⚡
可以看到,预聚合对聚合查询(SUM/COUNT/GROUP BY)的提升最为显著——7 倍,因为 DuckDB 不再需要扫描整个数据集。
五、三种增量更新策略
预聚合表不是"一劳永逸"的。当新订单数据进来时,你需要更新汇总表。以下是三种常用策略:
策略一:定时全量重建(最简单)
-- 每天凌晨 2 点重建
DROP TABLE IF EXISTS daily_revenue;
CREATE TABLE daily_revenue AS
SELECT order_date, category, region,
SUM(amount) AS revenue, COUNT(*) AS orders
FROM orders
GROUP BY 1, 2, 3;
对于 100 万行以内的数据,DuckDB 重建一张预聚合表只需几百毫秒,完全可以在后台静默执行。这是最简单、最可靠的策略,特别适合数据量不大且对实时性要求不高的场景。
策略二:增量追加(适合数据持续增长)
-- 只插入新日期的数据
INSERT INTO daily_revenue
SELECT order_date, category, region,
SUM(amount) AS revenue, COUNT(*) AS orders
FROM orders
WHERE order_date > (SELECT MAX(order_date) FROM daily_revenue)
GROUP BY 1, 2, 3;
增量追加避免了全量重建的开销,但需要注意:如果原始数据可能被修改或删除,增量策略会产生不一致。
策略三:CTE + UNION(适合临时查询)
WITH base AS (
SELECT * FROM daily_revenue
UNION ALL
SELECT order_date, category, region, SUM(amount), COUNT(*)
FROM orders
WHERE order_date > '2024-06-01'
GROUP BY 1, 2, 3
)
SELECT * FROM base;
这种方式不修改预聚合表,而是将汇总表和新数据临时合并查询。适合一次性分析或过渡期使用。
六、对比表:预聚合 vs 其他优化手段
| 优化手段 | 适用场景 | 实施难度 | 效果 | 维护成本 |
|---|---|---|---|---|
| 预聚合表 | 固定维度的高频聚合查询 | ⭐ 低 | ⭐⭐⭐⭐⭐ | ⭐⭐ 中 |
| 物化视图 | 复杂查询的长期缓存 | ⭐⭐ 中 | ⭐⭐⭐⭐ | ⭐⭐⭐ 高 |
| 索引优化 | 点查和范围查询 | ⭐⭐ 中 | ⭐⭐⭐ | ⭐ 低 |
| 加大内存 | 通用加速 | ⭐ 低 | ⭐⭐ | ⭐ 高(成本) |
| Parquet 分区 | 按时间范围过滤 | ⭐⭐ 中 | ⭐⭐⭐ | ⭐⭐ 中 |
结论:预聚合表是性价比最高的优化手段——实施简单、效果显著、维护成本低。
七、进阶:Python 集成自动化
在实际项目中,预聚合表的创建和更新通常会集成到 Python 脚本中:
import duckdb
import schedule
import time
def refresh_daily_revenue():
"""每天凌晨重建预聚合表"""
conn = duckdb.connect(':memory:')
conn.execute("CREATE TABLE orders AS SELECT * FROM read_parquet('orders/*.parquet')")
conn.execute("""
CREATE OR REPLACE TABLE daily_revenue AS
SELECT order_date, category, region,
SUM(amount) AS revenue, COUNT(*) AS orders,
AVG(amount) AS avg_order_value
FROM orders
GROUP BY 1, 2, 3
""")
conn.close()
print(f"[{time.strftime('%Y-%m-%d %H:%M')}] 预聚合表已更新")
# 每天凌晨 2 点执行
schedule.every().day.at("02:00").do(refresh_daily_revenue)
while True:
schedule.run_pending()
time.sleep(60)
配合 cron 或 systemd timer,这套系统可以 7×24 小时无人值守运行。
八、什么时候不该用预聚合?
预聚合不是银弹。以下情况不建议使用:
- 查询维度多变:如果你的 Dashboard 需要随时按任意字段组合筛选,预聚合表会变成"笛卡尔积噩梦"——你需要为每种组合建一张表。
- 数据量很小:如果表只有几千行,DuckDB 直接查询比维护预聚合表的开销更小。
- 实时性要求极高:预聚合表有延迟,不适合需要秒级更新的实时监控场景。
最佳实践:预聚合最适合"固定的几个 Dashboard 视图,查询频率高,数据量大(10 万行以上)“的场景。
九、变现建议
掌握了预聚合技巧后,你可以进一步构建真正能赚钱的数据产品:
电商销售 Dashboard:用预聚合表支撑秒级响应的 BI 面板,卖给中小电商卖家。每月收费 200-500 元,单个客户维护成本几乎为零。
财务报表自动生成 SaaS:每月自动从交易流水生成预聚合报表,作为 SaaS 服务收费。定价 99-299 元/月/企业,边际成本趋近于零。
API 数据产品:将预聚合结果暴露为 REST API,按调用次数计费。适合为第三方应用提供数据增强服务。
数据分析外包服务:用预聚合方案帮企业优化慢查询,按项目收费 5000-30000 元/项目。一次实施,长期受益。
付费教程/模板:将预聚合方案封装成可复用的 SQL 模板和教程,在知识星球或 Gumroad 上出售。
预聚合是性能优化的起点,不是终点。当你把预聚合表和 DuckDB 的 JSON 输出、Parquet 写入结合,就能构建一个完整的自助数据分析平台——这才是真正的变现利器。

📖 预聚合表的完整实战教程(含 Docker 部署、定时刷新脚本、API 封装)已在 duckdblab.org 发布,包含更多行业案例和性能调优细节。
💡 想系统掌握 DuckDB 的性能优化技巧?duckdblab.org 上有从入门到进阶的完整教程系列。