Redshift 数据仓库:列式存储与分布键

FreeGuideOnline 最新 2026-06-17

Redshift 数据仓库核心概念:列式存储与分布键

Amazon Redshift 是一款完全托管的 PB 级云数据仓库,专为大规模并行处理(MPP)而设计。理解其底层两大基石——列式存储分布键——是高效建模和查询优化的关键。本教程将深入剖析这两个概念,并提供最佳实践指南,帮助你构建高性能的数据仓库。

一、为什么选择 Redshift?

在深入技术细节之前,我们先明确 Redshift 适合解决什么问题。当你的数据量达到 TB 甚至 PB 级别,传统关系型数据库(如 MySQL、PostgreSQL)的查询性能会急剧下降。Redshift 通过以下方式实现极速分析:

  • 列式存储:针对聚合查询大幅降低 I/O。
  • 并行处理:将查询任务分发到多个计算节点。
  • 智能压缩:按列特性选择最高效的压缩算法。
  • 结果缓存:对重复查询自动返回缓存结果。

在本教程中,我们聚焦于列式存储如何减少扫描数据量,以及分布键如何最小化节点间的网络传输。

二、深入列式存储

什么是列式存储?

传统数据库(如 PostgreSQL)以行式组织数据,将一行中所有字段顺序存放在一起。Redshift 则采用列式组织,将表中每一列的数据独立存储。

假设我们有一张用户行为表,包含三列:user_idevent_datepage_views

  • 行式存储磁盘布局:
[1001, 2024-01-01, 5], [1002, 2024-01-02, 8], [1003, 2024-01-02, 12], ...
  • 列式存储磁盘布局:
user_id 数据块: [1001, 1002, 1003, ...]
event_date 数据块: [2024-01-01, 2024-01-02, 2024-01-02, ...]
page_views 数据块: [5, 8, 12, ...]

列式存储的核心优势

  1. 减少 I/O:聚合查询往往只需访问少数几列。例如,计算总浏览量 SELECT SUM(page_views) FROM events,只需扫描 page_views 列所在的数据块,完全忽略其他列。相比行式存储必须读取全部字段,I/O 量可减少 90% 以上。
  2. 高效压缩:同一列的数据类型和取值范围高度一致,压缩算法效果极佳。Redshift 会自动为每一列选择最佳压缩编码(如 Runlength、Delta、Zstandard)。高压缩率不仅节省存储成本,还因为读取更少的数据块而提升查询速度。
  3. 向量化处理:CPU 可一次性对同一列的一批数据执行相同操作,充分利用现代 CPU 的 SIMD 指令集,加速过滤、运算和聚合。

Redshift 数据块与区域映射

Redshift 将列数据划分为 1 MB 的不可变块,并保存在区域映射中:记录每个块的最小值、最大值等元数据。当查询包含 WHERE event_date >= '2024-01-15' 时,优化器会先检查区域映射,直接跳过那些最大值仍小于 2024-01-15 的块,实现精细化的裁剪查询,这是列式存储高效的另一重保障。

三、理解 Redshift 的分布键

Redshift 由领导节点和多个计算节点组成。计算节点又划分为若干切片(Slice),每个切片独立处理部分数据。当数据被加载到表中时,需要决定如何在切片之间分布行,这就引出了分布键的概念。

三种分布方式

创建表时,DISTSTYLE 子句定义了分布策略:

  • DISTSTYLE KEY (列):指定某一列作为分布键,其哈希值决定该行所属的切片。相同分布键值的记录必然落在同一节点。
  • DISTSTYLE EVEN:默认方式,行以轮询方式均匀分布到所有切片。没有明确的分布键。
  • DISTSTYLE ALL:将整个表完整复制到所有计算节点。通常用于极小表(维度表),避免广播开销。
  • DISTSTYLE AUTO:让 Redshift 自动选择。通常不建议依赖,理解原理后手动指定更可靠。

分布键对连接(JOIN)性能的决定性作用

MPP 架构下,连接操作会引发数据在节点间移动,这是最耗资源的操作之一。数据移动有两种类型:

  1. 广播(Broadcast):将其中一张表的全部副本发送到每个节点。适用于一张表很小的情况。
  2. 重组分布(Redistribution):两张表都根据连接键重新分布。如果无表采用连接键作为分布键,则两张表的数据都需要跨网络传输。

最佳场景:当连接键同时也是两张表的分布键时,由于相同键值的行已在同一节点,连接可以完全本地执行,无需网络传输,性能最高。

选择分布键的黄金法则

  1. 以连接键为首选:选择经常在大表连接中使用的列。例如,数据仓库最常见的是事实表与维度表连接,且事实表通常很大,因此应选择事实表中连接维度表的键作为分布键(如 customer_idproduct_id)。
  2. 避免数据倾斜:如果某个键值的行数占比过高(例如 user_id 中的 NULL 或超级用户),会导致某些切片数据量远超其他切片,产生“热切片”,降低并行效率。应确保分布键的基数足够高,且值分布均匀。
  3. 兼顾分组与聚合:查询中频繁出现 GROUP BY 的列,若与分布键相同,聚合可在本地执行,避免再次重分布(Redshift 会在 GROUP BY 键与分布键一致时启用流聚合)。
  4. 不要使用日期或时间戳列:这类列通常用作过滤条件,而不是连接键;分布到该列会导致数据按时间顺序写入,易引起热点;更重要的是,典型查询需要扫描时间范围,数据如果按时间分布,每个切片都能参与并行扫描,反而是优势。因此,时间列适合作为排序键,不适合作为分布键。

实战示例

假设你的电商数据模型包含:

  • 订单事实表 orders (数十亿行),列:order_id, customer_id, order_date, amount
  • 客户维度表 customers (百万行),列:customer_id, name, segment

最优设计:

CREATE TABLE customers (
    customer_id BIGINT NOT NULL,
    name VARCHAR(100),
    segment VARCHAR(50)
)
DISTSTYLE ALL;  -- 维度表全复制,避免80%的连接传输

CREATE TABLE orders (
    order_id BIGINT NOT NULL,
    customer_id BIGINT NOT NULL,
    order_date DATE,
    amount DECIMAL(18,2)
)
DISTSTYLE KEY
DISTKEY(customer_id)  -- 事实表按 customer_id 分布
COMPOUND SORTKEY(order_date, customer_id);  -- 排序键优化时间范围查询

这样,当执行 SELECT c.segment, SUM(o.amount) FROM orders o JOIN customers c ON o.customer_id = c.customer_id WHERE o.order_date BETWEEN '2024-01-01' AND '2024-01-31' GROUP BY c.segment 时:

  • customers 存在于每个节点,无需传输。
  • orders 已按 customer_id 分布,与 customers 的连接在本地完成。
  • group by c.segment 需要轻度聚合,Redshift 会对本地数据先做预聚合再汇总,大幅减少数据移动。

四、列式存储与分布键的协同效应

优秀的设计让两者优势叠加:

  • 列式存储让只读取 amountorder_date 列的扫描飞快。
  • 分布键使连接无数据移动,避免网络瓶颈。
  • 排序键(Sort Key)允许对 order_date 进行范围裁剪。 每一层优化都在减少不必要的 IO 和网络消耗,最终实现亚秒级查询响应,即使在数百 TB 数据规模下。

五、常见误区和监控

  • 误区1:分布键应为主键。主键通常为唯一标识,但若主键不是连接键,用它分布将导致几乎所有 JOIN 都涉及重分布。选择连接键,而不是 ID。
  • 误区2:EVEN 分布最安全。对于大表,EVEN 分布虽然消除了倾斜风险,但也让所有大表连接都必须跨节点传输,性能最差。只在确实找不到合适分布键的小表上使用。
  • 误区3:忽略排序键。分布键解决“行在哪个节点”,排序键解决“行在节点内怎么排序”。两者独立,需分别优化。

监控查询:审核查询计划中的 DS_DIST_ALL_NONE(无传输)和 DS_DIST_BOTH(两表都重分布),倾向于前者。使用 STL_ALERT_EVENT_LOG 检查数据倾斜。

六、动手练习:验证你的选择

  1. 在 Redshift 中创建三张测试表:一张按连接键分布,一张按 EVEN 分布,一张错误分布。
  2. 加载具有倾斜特征的模拟数据。
  3. 使用 EXPLAIN 分析同一个 JOIN 查询的计划,观察 XN Hashed JoinXN Hash Join 的区别及数据移动操作。
  4. 使用 SVV_TABLE_INFO 查看每个表的倾斜情况(skew_rows 列)。

通过实际对比,你会更加深刻地理解分布键的威力。

七、总结

  • 列式存储是 Redshift 高性能的基础,通过只读相关列、高压缩率和元数据裁剪来最小化磁盘 I/O。
  • 分布键决定了数据在集群切片间的放置方式,正确选择可以消除连接时的网络 I/O,实现本地计算。
  • 设计时始终从事实表与维度表的连接键出发,选择基数高、分布均匀且频繁出现在 JOIN 和 GROUP BY 中的列作为分布键。
  • 将小维度表设为 DISTSTYLE ALL,大事实表使用 KEY 分布,并配合适当的排序键,搭建稳健的数据仓库模型。

现在,你可以打开 Redshift 查询编辑器,开始用这些原则优化自己的表结构了。记住:没有一劳永逸的分布键,持续监控查询性能并调整才是正道。