以StarRocks为例讲解MPP架构和列式存储

一、 MPP架构和列式存储概念讲解

MPP 架构列式存储这两项技术是现代高性能数据分析数据库(如 StarRocks、ClickHouse、Snowflake 等)的基石,理解了它们,你就明白了为什么这些数据库能如此之快。


1. MPP 架构 (Massively Parallel Processing)

什么是 MPP?

MPP 即大规模并行处理。你可以把它想象成一个分工极其明确、协作效率极高的"工厂流水线"。

  • 传统数据库(如 MySQL):像一个老师傅,所有工作(接单、切菜、炒菜、装盘)都自己一个人完成。虽然也能做,但订单一多就忙不过来,成为瓶颈。
  • MPP 数据库:像一条现代化的流水线,有无数个工人(节点)同时工作。每个工人只负责处理一道工序,大家齐心协力,同时处理海量任务。
MPP 的核心思想:"分而治之"

MPP 架构将一个大型集群(多台服务器)中的每个节点都视作一个独立的计算和存储单元。没有一个全局的主节点会成为瓶颈。

  1. 数据分布 :一张巨大的表(比如 100TB)的数据并不是存在一台机器上,而是被水平切分 ,分布到集群的数十、数百甚至数千个节点上。每个节点只存有一小片数据。
    • 常用策略 :按照某个键(如 user_id)进行 Hash 分片,保证相同 user_id 的数据落在同一个节点上。
  2. 查询执行 :当你执行一个查询时(例如 SELECT COUNT(*) FROM big_table):
    • 查询协调:一个主节点(Coordinator)接收你的 SQL 请求,并生成一个分布式执行计划。
    • 并行计算 :它将这个计划下发到所有存有相关数据分片的节点上
    • 本地计算 :每个节点在自己的那一小份数据上同时地、并行地执行这个计算(比如各自数自己那里有多少行)。
    • 结果汇总:各个节点将本地计算出的中间结果(比如各自的计数)返回给主节点。
    • 最终聚合:主节点将这些中间结果汇总成最终结果(把所有计数加起来),返回给用户。
MPP 的优势:
  • 高性能:多个节点并行工作,极大地缩短了处理时间。理论上,节点数量增加一倍,处理速度就能接近提升一倍(线性扩展)。
  • 高扩展性:可以通过增加廉价的普通服务器(节点)来轻松扩展整个集群的处理能力,以应对数据量和计算量的增长。
  • 无单点瓶颈:计算和存储都分散在各节点,没有共享资源争用的问题。
MPP 的劣势:
  • 延迟敏感:节点间需要频繁交换数据(Shuffle),因此对网络延迟要求很高,通常要求高速局域网。
  • 数据倾斜 :如果数据分片策略不好(比如某个 user_id 的数据特别多),会导致某个节点任务过重,成为"短板",影响整体速度。

2. 列式存储 (Columnar Storage)

什么是列式存储?

它与传统的行式存储相对应。

  • 行式存储 :像把一行的所有数据(用户ID、姓名、年龄、地址...)紧紧挨着存在一起。类似于"记事本",一行一行地记录。
    • 适用场景 :适合事务处理(OLTP),如频繁插入、更新、删除整行数据的操作。例如 SELECT * FROM users WHERE user_id = 123
  • 列式存储 :像把同一列的所有数据存在一起。所有用户ID存在一个地方,所有姓名存在另一个地方,所有年龄又存在别的地方。类似于"成绩单",先列出所有学生的语文成绩,再列出所有学生的数学成绩。
    • 适用场景 :适合分析处理(OLAP),如对某几列进行聚合、筛选、计算。例如 SELECT AVG(age), department FROM users GROUP BY department
列式存储的优势:
  1. 极高的压缩比:同一列的数据类型相同,数据模式相似(比如都是年龄数字,或者都是省份字符串),可以使用针对性的压缩算法(如 Run-Length Encoding、Delta Encoding、Dictionary Encoding),获得极高的压缩率。压缩不仅能节省存储空间,更能减少磁盘 I/O,因为从磁盘读出的数据块更小。
  2. 极少的 I/O 读取 :分析查询通常只关心少数几个列。例如,计算平均年龄的查询只需要读取 age 这一列。
    • 行式存储 :必须把整行数据(包括不需要的 name, address 等)从磁盘读入内存,浪费大量 I/O 带宽。
    • 列式存储 :只需要读取 age 这一列的数据,I/O 效率极高。
  3. 更适合向量化执行:由于数据按列连续存储,CPU 可以一次性将一整列数据加载到高速缓存中,并利用 SIMD(单指令多数据)指令进行并行计算。这就像从"用勺子一粒一粒吃饭"变成了"用铲子一铲一铲吃饭",极大地提高了 CPU 的计算效率。
列式存储的劣势:
  • 不适合频繁更新/点查:更新一行数据需要重写多个列的文件,成本很高。点查(查询单条记录的所有字段)也需要从多个列文件中读取数据并组装,性能较差。

3. 强强联合:MPP + 列式存储

像 StarRocks 这样的数据库,将两者结合,发挥了"1+1 >> 2"的效果:

  1. 数据存储 :数据按列 进行存储,并且被水平切分 分布到 MPP 集群的各个节点上。
  2. 查询处理 :当一个分析查询到来时:
    • MPP 架构的主节点生成分布式计划。
    • 每个数据节点 并行地在自己本地的列式数据上进行操作。
    • 由于是列式存储,每个节点只读取查询所需的列 ,并利用向量化执行引擎极快地完成聚合、过滤等操作。
    • 最后将中间结果汇总,得到最终答案。
案例对比:

假设有一张 10 亿行、100 列的表,要执行 SELECT province, COUNT(*) FROM sales WHERE year = 2023 GROUP BY province

  • 传统行式数据库 :可能需要从磁盘读取所有 100 列10 亿行 数据(数据量巨大),然后在内存中过滤出 year=2023 的行,再对 province 分组计数。速度极慢。
  • MPP + 列式数据库
    1. 主节点将任务分发给所有存储节点。
    2. 每个节点只读取 本地的 yearprovince 这两列的数据(I/O 量减少 98%)。
    3. 每个节点利用向量化执行,并行地过滤 year=2023 的数据,并对 province 进行本地计数。
    4. 各节点将(省份,计数)的中间结果发给主节点。
    5. 主节点做最终汇总。速度极快。

总结

特性 MPP 架构 列式存储
核心思想 分而治之,并行处理 按列组织数据
优化目标 计算速度,通过增加节点线性扩展性能 查询和压缩效率,减少 I/O,加速聚合
好比 工厂流水线,众人拾柴火焰高 成绩单,看单科成绩非常方便
劣势 网络要求高,怕数据倾斜 不适合频繁更新和点查

两者珠联璧合,共同造就了 StarRocks 在海量数据场景下极致的速度体验,使其成为实时数据分析的利器。

二、利用 MPP 和列式存储的特性进行表设计

在 StarRocks 中建表是性能调优的第一步,也是最关键的一步。巧妙地利用 MPP 和列式存储的特性进行表设计,能带来成倍的性能提升。

以下是如何在建表时更好地利用这两大特性的详细指南和最佳实践。


核心思路

  1. 利用 MPP:关键在于"数据分布"
    • 目标:让数据均匀分散在各个 BE 节点上,最大化并行计算能力;并尽量减少节点间数据传输(Shuffle)。
    • 手段 :精心选择分桶键(DISTRIBUTED BY HASH)
  2. 利用列式存储:关键在于"降低I/O"和"加速计算"
    • 目标:减少扫描数据量、提高压缩比、优化本地计算效率。
    • 手段 :选择合适的数据类型、编码、压缩 ,并设计分区排序键

1. 利用 MPP 架构进行数据分布设计

分桶(Bucketing) - DISTRIBUTED BY HASH(...)

这是实现 MPP 并行计算的基石。数据通过分桶键的 Hash 值被分散到不同的分桶(Tablet) 中,每个分桶都是数据移动、复制和计算的最小单元。

如何选择分桶键(Bucket Key)?

  • 原则一:选择高基数列
    • 为什么:确保数据能尽可能均匀地分布到各个分桶,避免数据倾斜。如果一个分桶的数据量远大于其他分桶,它就会成为查询的瓶颈。
    • 好的选择user_id, order_id, device_id 等唯一性或基数很高的列。
    • 坏的选择性别省份(基数低,会导致数据严重倾斜)、NULL(所有NULL值会hash到同一个桶)。
  • 原则二:选择频繁用于查询过滤或连接的列
    • 为什么 :StarRocks 的本地计算能力很强。如果查询条件或 JOIN 条件中包含分桶键,可以极大减少节点间的数据传输(Shuffle)。
      • 例如,如果按 user_id 分桶,那么 WHERE user_id = 1001 的查询可以精准地只路由到一个分桶进行计算(Pruning)。
      • 如果两个大表都按相同的列(如 user_id)且相同桶数分桶,那么它们之间的 JOIN 操作可以采取 Colocate Shuffle 策略,直接在本地进行关联,效率极高。

如何确定分桶数量?

  • 推荐范围 :单个分桶的数据量建议在 100MB ~ 1GB 之间。
  • 计算公式分桶数量 ≈ 总数据量预估 / 1GB
  • 限制:分桶数量一旦确定,后续不易修改。建表时需做好预估。对于中小表,建议设置不少于 8 个分桶以保证足够的并行度。

示例:

sql

SQL 复制代码
CREATE TABLE user_behavior (
    user_id INT,
    item_id INT,
    ...
)
DUPLICATE KEY(user_id, item_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 12 -- 选择高基数的user_id作为分桶键,并设置12个分桶
PROPERTIES ("replication_num" = "3");

2. 利用列式存储进行存储优化

选择合适的数据类型
  • 使用最小、最高效的数据类型
    • 使用 INT 而不是 BIGINT(如果数值范围允许)。
    • 使用 VARCHAR(n) 而不是 TEXT/STRING,并指定合适的长度。
    • 使用 DATEDATETIME 而不是字符串来表示时间。
  • 为什么:更小的数据类型占用更少的磁盘和内存空间,列式存储压缩效率更高,查询时在内存中能处理更多数据,CPU Cache 更友好。
使用编码和压缩 - encodingcompression

StarRocks 会自动为列选择编码和压缩(如 LZ4),但你也可以手动指定以优化性能。

  • 低基数列(如枚举值、状态码) :使用 BITMAP_ENCODINGLOWCARD_DICT_ENCODING,压缩比极高。

  • 高基数列(如ID、时间戳) :使用 PLAIN_ENCODINGDELTA_ENCODING

  • 可以在建表时指定:

    sql

    SQL 复制代码
    PROPERTIES (
        "replication_num" = "3",
        "compression" = "LZ4", -- 表级别的压缩算法
        "storage_format" = "v2" -- 使用新版存储格式,通常更好
    );
排序键和前缀索引 - DUPLICATE/PRIMARY/UNIQUE KEY(...)

在 StarRocks 中,DUPLICATE KEYPRIMARY KEYUNIQUE KEY 决定了数据的排序和存储方式

  • 排序键(Sort Key):数据在每个分桶内是按照这些键排序后存储的。
  • 前缀索引(Prefix Index):StarRocks 会为每 1024 行数据生成一个前缀索引项,内容是排序键的前 36 个字节。查询时,可以先通过前缀索引快速定位到数据块,再在块内进行精准查找。

如何设计排序键?

  • 原则:将最常用作查询条件的列放在前面
  • 为什么 :排序和前缀索引可以极大地加速范围查询和等值查询。
    • 查询 WHERE date = '2023-01-01' AND user_id = 100,如果 (date, user_id) 是排序键,可以快速定位到数据所在区域,极大减少需要扫描的数据行数

示例:

sql

SQL 复制代码
CREATE TABLE user_behavior (
    date DATE,
    user_id INT,
    item_id INT,
    behavior_type VARCHAR(10),
    timestamp BIGINT
)
DUPLICATE KEY(date, user_id, item_id) -- 排序键:日期 -> 用户 -> 商品
DISTRIBUTED BY HASH(user_id) BUCKETS 12;

这个设计使得 WHERE date = '2023-01-01' 这类按天查询的效率非常高。


3. 分区(Partitioning) - PARTITION BY RANGE(...)

分区不是 MPP 的直接特性,但它是列式存储查询加速的重要补充

  • 作用 :分区主要用于数据管理查询加速(分区剪枝)。
  • 如何工作 :按照时间(通常是天)或其他范围将数据划分成不同的子目录。查询时,优化器可以根据条件直接跳过无关的分区,减少需要扫描的数据量。

分区键选择建议:

  • 几乎总是使用时间字段 :如 datedt
  • 分区数量不宜过多 :通常按天分区,单个分区数据量建议在 1GB 以上。过多的小分区会浪费元数据管理开销,影响 FE 性能。

示例:

sql

SQL 复制代码
CREATE TABLE user_behavior (
    date DATE, -- 分区键
    user_id INT,
    ...
)
DUPLICATE KEY(date, user_id, ...)
PARTITION BY RANGE(date) -- 按天分区
(
    PARTITION p20230101 VALUES [('2023-01-01'), ('2023-01-02')),
    PARTITION p20230102 VALUES [('2023-01-02'), ('2023-01-03')),
    ...
)
DISTRIBUTED BY HASH(user_id) BUCKETS 12;

查询 WHERE date BETWEEN '2023-01-01' AND '2023-01-07' 只会扫描 p20230101p20230107 这 7 个分区,而不是全表。


综合实战案例:电商订单表

假设我们要创建一张订单事实表。

sql

SQL 复制代码
CREATE TABLE dwd_order_fact (
    -- 时间维度 (分区键 & 排序键首位)
    dt DATE COMMENT "订单日期",
    order_time DATETIME COMMENT "订单时间",

    -- 业务维度 (高基数,作为分桶键和排序键)
    order_id BIGINT COMMENT "订单ID",
    user_id BIGINT COMMENT "用户ID",

    -- 其他维度
    product_id INT COMMENT "商品ID",
    province_code SMALLINT COMMENT "省份编码",

    -- 指标(度量)
    quantity INT COMMENT "数量",
    revenue DECIMAL(10,2) COMMENT "订单金额",
    coupon_amt DECIMAL(10,2) COMMENT "优惠券金额"

)
ENGINE=OLAP
-- 1. 列式存储优化:排序键设计,优先常用查询条件
DUPLICATE KEY(dt, order_id, user_id)
-- 2. MPP优化:选择高基数列user_id作为分桶键,数据均匀分布
DISTRIBUTED BY HASH(user_id) BUCKETS 16
-- 3. 分区管理:按天分区,方便管理并加速按天查询
PARTITION BY RANGE(dt)
(
    START ("2023-01-01") END ("2024-01-01") EVERY (INTERVAL 1 DAY)
)
-- 4. 存储属性设置
PROPERTIES (
    "replication_num" = "3",
    "storage_format" = "v2",
    "compression" = "LZ4"
);

设计解读:

  1. MPP并行 :数据按 user_id Hash 分布到 16 个桶,均匀分散在集群各节点,并行计算。
  2. 列式存储
    • (dt, order_id, user_id) 排序,对按天查询和按订单/用户查询极度友好。
    • 使用合适的数据类型(如 SMALLINT 表示省份)。
    • 启用压缩。
  3. 分区裁剪:按天分区,查询特定日期范围时性能极佳。
  4. Colocate Join :如果用户维度表也按 user_id 分桶且桶数相同,两表关联时可避免Shuffle。

通过这样的设计,无论是面向用户的行为分析、按天的报表统计,还是多表关联查询,都能充分发挥 StarRocks MPP 和列式存储的威力。

相关推荐
CodeCraft Studio9 小时前
借助 TX Text Control:在 .NET C# 中使用 PDF/A-3b 创建可信文档容器
大数据·pdf·数字签名·tx text control·pdf/a-3b·pdf文档开发
R瑾安9 小时前
mysql集群部署(Mysql Group Replication)
数据库·mysql·wpf
周小码9 小时前
pgroll:简化PostgreSQL零停机迁移
数据库·postgresql
代码的余温9 小时前
解析SQL Server核心服务与功能
数据库·sqlserver
没刮胡子9 小时前
mysql分页SQL
数据库·sql·mysql
fatsheep洋9 小时前
sql项目总结
数据库·sql
高级测试工程师欧阳9 小时前
SQLint3 模块如何使用
数据库·python·mysql·oracle
ActionTech9 小时前
2025 年 8 月《大模型 SQL 能力排行榜》发布
数据库·sql·oracle
在未来等你10 小时前
Elasticsearch面试精讲 Day 8:聚合分析与统计查询
大数据·分布式·elasticsearch·搜索引擎·面试