ClickHouse Sharding 分片与 Partitioning 分区:区别、联系与生产实践

文章目录

  • 一、一句话区分
  • [二、图解:Sharding + Partitioning 组合](#二、图解:Sharding + Partitioning 组合)
  • 三、详细对比
  • 四、分片的完整设置流程
    • [4.1 第一步:集群配置(一次性配置)](#4.1 第一步:集群配置(一次性配置))
    • [4.2 第二步:建表时指定分片键](#4.2 第二步:建表时指定分片键)
    • [4.3 分片键不可修改](#4.3 分片键不可修改)
  • [五、在 ClickHouse 中的具体实现](#五、在 ClickHouse 中的具体实现)
    • [5.1 分区(Partitioning):本地表的物理切分](#5.1 分区(Partitioning):本地表的物理切分)
    • [5.2 分片(Sharding):分布式表的跨节点路由](#5.2 分片(Sharding):分布式表的跨节点路由)
    • [5.3 两者配合的查询执行流程](#5.3 两者配合的查询执行流程)
  • 六、生产环境组合案例
    • [6.1 场景:用户行为日志表](#6.1 场景:用户行为日志表)
    • [6.2 完整建表流程](#6.2 完整建表流程)
    • [6.3 效果](#6.3 效果)
  • 七、关于轮询写入的问题(你的场景)
  • 八、常见误区与澄清
  • 九、选择建议
  • 十、总结

在 ClickHouse 集群中,"分片"(Sharding)和"分区"(Partitioning)是两个极易混淆的核心概念。很多人误以为分区就是分片,或者认为两者是互斥的------实际上,它们是不同维度、可以协同工作的数据切分策略。本文将系统梳理两者的区别、联系,并通过生产案例展示如何组合使用,帮助你彻底理解并正确应用于实际项目。


一、一句话区分

概念 核心作用 数据范围 存储位置
Sharding(分片) 水平扩展,解决单机存不下、算不动的问题 不同数据在不同节点 跨节点(集群级别)
Partitioning(分区) 局部过滤,解决查询扫全表的问题 同一节点内的数据再切分 单节点内部(本地级别)

简单关系:一个大表 → 先按 Sharding 分散到不同节点 → 每个节点内部再按 Partitioning 切分成小块。


二、图解:Sharding + Partitioning 组合

复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                           分布式表(逻辑视图)                            │
│                          user_id 作为分片键                             │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
            ┌───────────────────────┼───────────────────────┐
            ▼                       ▼                       ▼
┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐
│     节点1(分片1)      │ │     节点2(分片2)      │ │     节点3(分片3)      │
│   user_id: 1,4,7...   │ │   user_id: 2,5,8...   │ │   user_id: 3,6,9...   │
│ ┌───────────────────┐ │ │ ┌───────────────────┐ │ │ ┌───────────────────┐ │
│ │ 分区1:2025-01-01 │ │ │ │ 分区1:2025-01-01 │ │ │ │ 分区1:2025-01-01 │ │
│ │ 分区2:2025-01-02 │ │ │ │ 分区2:2025-01-02 │ │ │ │ 分区2:2025-01-02 │ │
│ │ 分区3:2025-01-03 │ │ │ │ 分区3:2025-01-03 │ │ │ │ 分区3:2025-01-03 │ │
│ │ ...               │ │ │ │ ...               │ │ │ │ ...               │ │
│ └───────────────────┘ │ │ └───────────────────┘ │ │ └───────────────────┘ │
└───────────────────────┘ └───────────────────────┘ └───────────────────────┘
          ↑                               ↑                               ↑
    Partitioning(本地切分)        Partitioning(本地切分)        Partitioning(本地切分)

三、详细对比

对比维度 Sharding(分片) Partitioning(分区)
目标 突破单机容量/性能瓶颈 减少查询扫描的数据量
粒度 节点级(跨机器) 文件级(单机内部)
对查询影响 决定去哪个节点查 决定查哪个文件块
对写入影响 决定数据去哪个节点 决定数据进哪个文件块
典型键 用户ID、订单ID(高基数列) 日期(时间列)
ClickHouse 语法 Distributed 表引擎 + 分片键 PARTITION BY
副本关系 每个分片可有多个副本 分区是副本内部的子集
设置时机 建表时(不可改) 建表时可改(需迁移)
是否可修改 ❌ 不可修改,需重建表 ⚠️ 可改,但需重分区

四、分片的完整设置流程

很多人以为分片只是建表时写个 Distributed 引擎就行了。实际上,分片的设置分为两步:集群配置 + 建表指定。

4.1 第一步:集群配置(一次性配置)

在 ClickHouse 配置文件中定义集群,指定节点与分片的归属关系。

配置文件位置/etc/clickhouse-server/config.d/cluster.xml(推荐)或 config.xml

xml 复制代码
<?xml version="1.0"?>
<clickhouse>
    <!-- 远程服务器配置:定义集群 -->
    <remote_servers>
        <!-- 集群名称,建表时会用到 -->
        <analytics_cluster>
            <!-- 分片1 -->
            <shard>
                <!-- 可选:是否内部复制(副本间自动同步) -->
                <internal_replication>true</internal_replication>
                <!-- 副本1 -->
                <replica>
                    <host>clickhouse-node01</host>
                    <port>9000</port>
                    <!-- 可选:优先级,用于负载均衡 -->
                    <priority>1</priority>
                </replica>
                <!-- 副本2 -->
                <replica>
                    <host>clickhouse-node02</host>
                    <port>9000</port>
                    <priority>2</priority>
                </replica>
            </shard>
            <!-- 分片2 -->
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                    <host>clickhouse-node03</host>
                    <port>9000</port>
                    <priority>1</priority>
                </replica>
                <replica>
                    <host>clickhouse-node04</host>
                    <port>9000</port>
                    <priority>2</priority>
                </replica>
            </shard>
        </analytics_cluster>
    </remote_servers>

    <!-- 可选:分片宏配置,用于副本表路径 -->
    <macros>
        <shard>01</shard>
        <replica>node01</replica>
    </macros>
</clickhouse>

配置说明

配置项 含义
remote_servers 集群配置的根节点
analytics_cluster 自定义集群名称(建表时使用)
shard 一个分片节点组
internal_replication 是否由 ClickHouse 自动同步副本(推荐 true
replica 分片内的一个物理节点
priority 优先级,数字越小越优先(配合 load_balancing=in_order 使用)
macros 宏变量,用于副本表路径(如 ReplicatedMergeTree

验证配置是否生效

sql 复制代码
-- 查看集群信息
SELECT * FROM system.clusters WHERE cluster = 'analytics_cluster';

-- 显示所有集群
SHOW CLUSTERS;

4.2 第二步:建表时指定分片键

集群配置好之后,需要在创建分布式表时指定分片键。

sql 复制代码
-- 1. 在每个节点上创建本地表(存真实数据)
CREATE TABLE user_log_local ON CLUSTER analytics_cluster
(
    event_date Date,
    user_id UInt64,
    event_type LowCardinality(String),
    page_url String
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/user_log', '{replica}')
PARTITION BY toYYYYMMDD(event_date)  -- 分区:按天
ORDER BY (user_id, event_date);

-- 2. 创建分布式表(指定分片键,作为查询入口)
CREATE TABLE user_log_distributed ON CLUSTER analytics_cluster
AS user_log_local
ENGINE = Distributed(
    'analytics_cluster',   -- 集群名称(与配置文件一致)
    'default',             -- 数据库名
    'user_log_local',      -- 本地表名
    user_id                -- 分片键:按 user_id 路由到不同分片
);

分片键的选择

分片键类型 示例 优点 缺点
高基数列 user_id 数据分布均匀,查询时易命中单分片 无明显缺点
随机数 rand() 绝对均匀 无法按业务裁剪分片
低基数列 city 分布不均,可能倾斜

4.3 分片键不可修改

sql 复制代码
-- ❌ 不支持修改分片键
-- 没有 ALTER 语法可以修改分布式表的分片键

为什么?

  • 数据已经按旧规则分布在各个分片上了
  • 改分片键意味着要重分布所有数据(成本极高)

如果需要修改

sql 复制代码
-- 1. 创建新分布式表(使用新分片键)
CREATE TABLE user_log_distributed_new AS user_log_local
ENGINE = Distributed('analytics_cluster', 'default', 'user_log_local', new_sharding_key);

-- 2. 迁移数据(从旧表插入到新表,会按新规则重新路由)
INSERT INTO user_log_distributed_new SELECT * FROM user_log_distributed;

-- 3. 删除旧表,重命名新表
DROP TABLE user_log_distributed;
RENAME TABLE user_log_distributed_new TO user_log_distributed;

五、在 ClickHouse 中的具体实现

5.1 分区(Partitioning):本地表的物理切分

sql 复制代码
-- 在每个节点上创建本地表,指定分区键
CREATE TABLE orders_local ON CLUSTER cluster
(
    event_date Date,
    user_id UInt64,
    amount Decimal(10,2)
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)  -- 按月分区
ORDER BY (user_id, event_date);

效果 :每个节点内部,数据按月份切分成不同的文件目录。查询 WHERE event_date = '2025-01-15' 时,只扫描对应月份的分区目录。

5.2 分片(Sharding):分布式表的跨节点路由

sql 复制代码
-- 创建分布式表(逻辑视图,不存数据)
CREATE TABLE orders_distributed AS orders_local
ENGINE = Distributed(
    'cluster',           -- 集群名(在配置文件中定义)
    'default',           -- 数据库名
    'orders_local',      -- 本地表名
    user_id              -- 分片键:不同 user_id 去不同节点
);

5.3 两者配合的查询执行流程

sql 复制代码
-- 查询:同时利用分片和分区裁剪
SELECT sum(amount) 
FROM orders_distributed 
WHERE event_date = '2025-01-15'   -- 分区裁剪
  AND user_id = 12345;            -- 分片裁剪
步骤 做了什么 利用的机制
1 根据 user_id = 12345 确定数据在哪个分片 Sharding
2 只向该分片发送查询 避免扫描所有节点
3 在该分片的节点内,根据 event_date 定位到具体分区 Partitioning
4 只扫描该分区文件 避免扫描整节点数据

六、生产环境组合案例

6.1 场景:用户行为日志表

需求

  • 日均 10 亿行数据
  • 查询通常是:某天 + 某个用户的行为
  • 需要保留 90 天

6.2 完整建表流程

1. 集群配置/etc/clickhouse-server/config.d/analytics_cluster.xml):

xml 复制代码
<clickhouse>
    <remote_servers>
        <analytics_cluster>
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                    <host>node01</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>node02</host>
                    <port>9000</port>
                </replica>
            </shard>
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                    <host>node03</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>node04</host>
                    <port>9000</port>
                </replica>
            </shard>
        </analytics_cluster>
    </remote_servers>
</clickhouse>

2. 本地表 + 分布式表

sql 复制代码
-- 本地表:按用户ID分片,按天分区
CREATE TABLE user_log_local ON CLUSTER analytics_cluster
(
    event_date Date,
    user_id UInt64,
    event_type LowCardinality(String),
    page_url String
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/user_log', '{replica}')
PARTITION BY toYYYYMMDD(event_date)  -- 按天分区
ORDER BY (user_id, event_date);

-- 分布式表
CREATE TABLE user_log_distributed ON CLUSTER analytics_cluster
AS user_log_local
ENGINE = Distributed(analytics_cluster, default, user_log_local, user_id);

6.3 效果

查询示例 分片裁剪 分区裁剪 扫描范围
WHERE user_id=123 AND event_date='2025-01-15' ✅ 命中 1 个分片 ✅ 命中 1 个分区 1 个分片的 1 天数据
WHERE user_id=123 ✅ 命中 1 个分片 ❌ 无 1 个分片的全部 90 天数据
WHERE event_date='2025-01-15' ❌ 全分片 ✅ 命中 1 个分区 全部分片的 1 天数据
无过滤 ❌ 全分片 ❌ 无 全部分片的全部数据

七、关于轮询写入的问题(你的场景)

你之前的场景提到:轮询写入导致低配节点成为短板

java 复制代码
// 轮询写入:绕过分布式表,自己控制去哪个节点
String[] nodes = {"node01", "node02", "node03", "node04"};
for (int i = 0; i < dataList.size(); i++) {
    String targetNode = nodes[i % nodes.length];
    writeToNode(targetNode, dataList.get(i));
}

问题根源 :这种方式绕过了分片键,数据分布由应用代码决定,而不是 ClickHouse 的分片规则。

写入方式 分片键是否生效 数据分布依据 是否推荐
通过分布式表写入 ✅ 生效 分片键计算 ✅ 推荐
轮询直连节点写入 ❌ 绕过 应用代码随机/轮询 ❌ 不推荐

解决方案 :改为通过分布式表写入,让 user_id 分片键自动路由。

java 复制代码
// 正确写法:通过分布式表写入
// 连接任意一个节点,写入分布式表即可
String jdbcUrl = "jdbc:clickhouse://任意节点:8123";
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
    PreparedStatement pstmt = conn.prepareStatement(
        "INSERT INTO user_log_distributed (user_id, event_date, event_type, page_url) VALUES (?, ?, ?, ?)"
    );
    // 设置参数...
    pstmt.executeUpdate();
}

八、常见误区与澄清

误区 真相
"分区就是分片" ❌ 分区是本地切文件,分片是跨节点切数据
"分片越多越好" ❌ 分片过多会导致小文件多、跨节点查询开销大
"分区粒度越细越好" ❌ 按小时分区会产生大量小分区,元数据膨胀
"有了分片就不需要分区了" ❌ 分片解决节点分布,分区解决节点内扫描效率,两者互补
"分片键和分区键必须相同" ❌ 可以不同,但设计时需考虑查询模式
"分片键可以事后修改" ❌ 不可修改,需重建表并迁移数据
"轮询写入和分片不冲突" ⚠️ 轮询写入绕过分片键,会导致数据分布不符合预期

九、选择建议

场景 是否需要分片 是否需要分区 原因
数据量 < 2TB ❌ 不需要 ✅ 需要 单机够用,用分区优化查询即可
数据量 2TB ~ 10TB ⚠️ 可选 ✅ 需要 单机可能够,但分区必须
数据量 > 10TB ✅ 必须 ✅ 需要 分片扩展容量,分区加速查询
查询总是带时间范围 可选 ✅ 强烈推荐 分区裁剪效果极佳
查询按用户ID精确分布 ✅ 推荐 可选 分片键设计合理可减少跨节点查询
查询经常跨多天全表聚合 ✅ 需要 ⚠️ 帮助有限 分片并行计算,分区帮助不大

十、总结

你关心的问题 答案
Sharding 解决什么问题? 数据量大到单机存不下时,通过分片分散到多节点
Partitioning 解决什么问题? 减少查询扫描的数据量,快速跳过无关文件块
两者是什么关系? 分工不同,可以同时使用。分片决定数据去哪个节点,分区决定节点内查哪个文件
ClickHouse 中如何实现? 分片用 Distributed 引擎 + 集群配置,分区用 PARTITION BY
分片什么时候设置? 集群配置时定义节点归属,建表时指定分片键
分片键能改吗? 不能,需要重建表并迁移数据
轮询写入的问题怎么解决? 改为通过分布式表写入,让分片键自动路由
生产环境怎么组合? 高基数列(如 user_id)做分片键,时间列做分区键

一句话记忆

分片(Sharding)是把数据"横着切"分到不同机器,解决"一台放不下";分区(Partitioning)是把同一台机器上的数据"竖着切"成小块,解决"一次查太多"。两者可以同时使用,互不冲突。分片在建分布式表时通过 sharding_key 设置,一旦定下就不可改。


如需深入了解 ClickHouse 的部署架构选型、分片与副本机制详解、分布式表原理剖析、无中心架构设计哲学、生产环境集群调优、多副本一致性实践、ClickHouse Keeper 核心原理等内容,请持续关注本专栏《ClickHouse 一站式从入门到实战》系列文章。

相关推荐
狼与自由2 天前
mysql到clickhouse
数据库·mysql·clickhouse
云天AI实战派2 天前
跨境出海全流程实战:用 Medusa + Hyperswitch + ClickHouse 搭建落地页、支付订阅、客服工单与多语言 SEO 闭环
大数据·人工智能·clickhouse·独立开发·跨境出海·medusa
海南java第二人3 天前
ClickHouse 实际应用类面试通关:项目案例、生产踩坑与实战经验
clickhouse·面试·实际应用类
meijinmeng4 天前
ClickHouse Kubernetes集群部署与维护文档
clickhouse
努力攻坚操作系统4 天前
ClickHouse详细教程
大数据·数据库·clickhouse
大帅点兵4 天前
设计一个金融交易监控系统
大数据·clickhouse·flink·spark·kafka·hbase
dinl_vin5 天前
FastAPI 系列 ·(十一):ClickHouse 集成——大数据查询实战
大数据·clickhouse·fastapi
麦兜和小可的舅舅6 天前
ClickHouse实时分布式集群设计方案选择探究
c++·分布式·clickhouse·kafka
海南java第二人7 天前
ClickHouse 性能优化完全指南:从数据模型到生产调优
clickhouse·性能优化