本章目标:能说清云数仓的存储计算分离架构,会用外部表和分区目录组织数据,并能估算一次查询的扫描成本和优化方向。 前置知识:数仓分层、OLAP 查询调优 预计用时:100 分钟
本地装一台数据库,数据量和并发一上来就撑不住;自己买服务器搭集群,又要提前预估容量,闲时资源浪费、忙时不够用。云数据仓库把存储和计算拆开卖,按实际用量付费,这是近几年数仓建设的主流形态。
1. 为什么需要云数据仓库
传统自建数仓有三个固定成本:
- 容量要提前买:为年底大促准备 20 台机器,平时大部分时间闲着。
- 扩容周期长:采购、上架、装环境,业务等不起。
- 存储和计算绑死:数据存三年,但只有月初跑报表时才算,磁盘一直占着钱。
云数仓的解法是把两层分开:
计算层(按需拉起、按秒/按量计费)
┌──────────┬──────────┬──────────┐
│ 虚拟集群 A │ 虚拟集群 B │ 虚拟集群 C │
└────┬─────┴────┬─────┴────┬─────┘
│ │ │
┌────▼──────────▼──────────▼─────┐
│ 对象存储(S3 / OSS / COS) │
│ 数据只存一份,长期保留 │
└─────────────────────────────────┘
数据放在对象存储里只有一份,计算集群可以随时扩缩。多个部门还能各自拉起集群,共享同一份数据,互不抢资源。
2. 核心概念
2.1 存储计算分离
| 层 | 存什么 | 怎么计费 | 特点 |
|---|---|---|---|
| 存储层 | 表文件、Parquet、日志 | 按 GB/月 | 便宜、持久、可无限扩 |
| 计算层 | 内存、临时文件、执行计划 | 按秒/按扫描量/按节点时 | 弹性、可随时关停 |
| 元数据层 | 库表结构、分区、权限 | 通常很便宜 | 统一管理多个计算集群 |
分离之后,关掉计算集群不丢数据,也不影响别人查询。代价是计算读数据要跨网络,延迟比本地磁盘高,所以云数仓会做缓存、预取和列裁剪。
2.2 外部表与内部表
- 内部表(Managed Table):数据由数仓管理,删表时数据一起删。
- 外部表(External Table):数据在对象存储的某个路径,表只记录路径和格式,删表不删数据。
外部表特别适合数据湖:原始日志由采集程序写进对象存储,数仓只建一张外部表去读。多个引擎(Spark、Trino、数仓)可以读同一份文件。
2.3 按量付费与包年包月
| 计费方式 | 适合 | 风险 |
|---|---|---|
| 按扫描量 | 查询量波动大、临时分析 | 一条 SELECT * 可能扫掉几百 GB |
| 按计算时长 | 固定时段的批处理 | 任务排队时也在计费 |
| 包年包月/预留 | 稳定高负载 | 预估过高会浪费 |
按扫描量计费时,优化 SQL 就是省钱。同一个结果,扫描 2 TB 和扫描 20 GB,账单差两个数量级。
2.4 主流产品对照
| 产品 | 存储 | 计算 | 常见场景 |
|---|---|---|---|
| MaxCompute | 阿里云盘古 | 按量/包年 | 国内离线数仓 |
| EMR + Hive/Spark | OSS/HDFS | 按节点 | 自建 Hadoop 生态 |
| Snowflake | 云对象存储 | 虚拟仓库 | 海外多租户分析 |
| BigQuery | 列式存储 | 无服务器 | 按扫描量计费 |
| Redshift | 托管存储 | 集群 | AWS 生态数仓 |
| ClickHouse Cloud | 对象存储 + 本地缓存 | 按节点 | 实时分析 |
产品名不同,核心模型一致:数据落在廉价存储,计算按需拉起,查询尽量少扫数据。
3. 动手示例
没有云账号也能验证这些概念。下面用 DuckDB 在本地模拟“对象存储 + 外部表 + 分区目录”。
3.1 把 CSV 当成外部表
CREATE OR REPLACE VIEW ext_orders AS
SELECT *
FROM read_csv_auto('data/sample/orders_demo.csv', header = true);
SELECT city, SUM(amount) AS gmv
FROM ext_orders
WHERE status = 'paid'
GROUP BY city
ORDER BY gmv DESC;
read_csv_auto 不导入数据,只按路径读取,行为类似外部表:文件还在原处,表只是访问入口。换成 read_parquet 就是云上更常见的列式格式。
3.2 按分区目录写 Parquet
云数仓通常用目录表达分区,例如 orders/dt=2024-01-01/part.parquet。DuckDB 可以直接生成这种布局:
COPY (
SELECT
*,
CAST(order_date AS DATE) AS dt
FROM read_csv_auto('data/sample/orders_demo.csv', header = true)
) TO 'data/lake/orders'
(FORMAT parquet, PARTITION_BY (dt), OVERWRITE_OR_IGNORE);
读取时开启 Hive 分区识别:
CREATE OR REPLACE VIEW lake_orders AS
SELECT *
FROM read_parquet(
'data/lake/orders/**/*.parquet',
hive_partitioning = true
);
SELECT dt, COUNT(*) AS rows, SUM(amount) AS gmv
FROM lake_orders
WHERE dt BETWEEN DATE '2024-01-01' AND DATE '2024-01-31'
GROUP BY dt
ORDER BY dt;
过滤 dt 时,引擎只打开匹配目录下的文件,其他月份的文件根本不会被读取。这就是分区裁剪。
3.3 估算扫描成本
假设某张表有 2 TB 数据,按扫描量计费,单价 0.03 元/GB:
| 查询写法 | 扫描量 | 费用 |
|---|---|---|
SELECT * 全表 |
2048 GB | 61.44 元 |
| 只查 1 个月分区 | 约 171 GB | 5.13 元 |
| 再只选 4 列(列存) | 约 30 GB | 0.90 元 |
计算公式:
费用 = 实际扫描 GB × 单价
实际扫描 = 总数据量 × 命中分区比例 × 命中列比例
注意“实际扫描”由引擎统计,不是文件总大小。压缩率高、列裁剪生效时,扫描量会明显小于磁盘占用。
3.4 生命周期管理
冷数据不必一直放在最贵的存储层。常见策略:
0–30 天 热数据,放标准存储,随时查询
31–180 天 温数据,转低频存储,查询频率下降
180 天以上 冷数据,转归档存储,查询前先解冻
对象存储一般支持生命周期规则,按前缀和天数自动转层或删除。数仓侧则按分区 DROP PARTITION 清理过期数据,避免元数据里堆着几万个空分区。
4. 常见坑
4.1 小文件太多
每批任务写几百个小文件,元数据操作和打开文件的开销超过读数据本身。按分区批量写、定期合并小文件,单文件控制在几十 MB 到几百 MB。
4.2 SELECT * 把账单打爆
列存只读需要的列。临时探索可以用 LIMIT 加采样,正式查询显式写列名。按扫描量计费的平台上,一条没加分区条件的查询可能直接产生一笔意外账单。
4.3 跨区域读数据
数据和计算在不同地域,每次扫描都走公网或跨区链路,慢且可能产生流量费。尽量让计算集群和存储桶在同一地域。
4.4 用外部表做高频点查
对象存储适合大范围扫描,不适合按主键查一行。高频点查应该进 KV/OLAP 数据库,而不是每次都去列文件。
4.5 忽略厂商锁定
各家的 SQL 方言、权限模型、存储格式都有差异。核心数据尽量用开放的 Parquet/ORC,元数据用统一 Catalog,降低迁移成本。
5. 在线练习
| 题目 | 考点 |
|---|---|
| 1193. 每月交易 I | 按月聚合与状态统计 |
| 1321. 餐馆营业额变化增长 | 时间窗口与累计指标 |
| 1341. 电影评分 | 多条件排序取 TopN |
自测:
- 能说出存储计算分离后,关掉计算集群数据为什么还在。
- 能解释分区裁剪和列裁剪分别减少了什么。
6. 面试延伸
面试题:什么是存储计算分离,解决了什么问题?
是什么:数据放对象存储,计算用独立集群按需拉起,两层分别扩缩和计费。
解决什么问题:存储和计算不再绑死,避免为峰值提前买机器;多团队共享一份数据,各自隔离计算资源。
我的实操边界:本章用 DuckDB 模拟外部表和分区目录,未开通云数仓账号做真实账单验证。
面试题:内部表和外部表怎么选?
是什么:内部表由数仓托管数据,外部表只登记对象存储路径。
解决什么问题:外部表让多个引擎共享同一份文件,删表不误删数据;内部表管理更省心,适合数仓自有的结果表。
我的实操边界:了解两者差异,未在生产环境配置外部表权限。
面试题:按扫描量计费时怎么控制成本?
是什么:分区裁剪、列裁剪、控制查询并发、限制 SELECT *、设置配额和预算告警。
解决什么问题:费用和扫描量直接挂钩,不规范的查询会放大账单。
我的实操边界:本章给出成本估算公式,未接入云厂商的预算系统。
面试题:小文件问题怎么产生、怎么处理?
是什么:频繁小批量写入产生大量小文件,读时打开和元数据开销大。用批量写、合并任务或写前攒批处理。
解决什么问题:文件数过多会让查询变慢,也会压垮元数据服务。
我的实操边界:了解合并思路,未处理过 TB 级小文件。
7. 本章验收
- 能画出存储计算分离的架构,并说明每层怎么计费。
- 能用 DuckDB 把 CSV 写成按日期分区的 Parquet,并读回验证。
- 能写出带分区过滤的查询,并解释为什么其他分区不会被扫描。
- 能根据总数据量、分区比例和列比例估算扫描量。
- 能说出内部表和外部表各适合什么数据。
- 能列出至少三条云数仓成本控制的措施。