欢迎光临
我们一直在努力

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

文章目录

  • 一、一句话区分
  • 二、图解:Sharding + Partitioning 组合
  • 三、详细对比
  • 四、分片的完整设置流程
    • 4.1 第一步:集群配置(一次性配置)
    • 4.2 第二步:建表时指定分片键
    • 4.3 分片键不可修改
  • 五、在 ClickHouse 中的具体实现
    • 5.1 分区(Partitioning):本地表的物理切分
    • 5.2 分片(Sharding):分布式表的跨节点路由
    • 5.3 两者配合的查询执行流程
  • 六、生产环境组合案例
    • 6.1 场景:用户行为日志表
    • 6.2 完整建表流程
    • 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 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)

验证配置是否生效:

— 查看集群信息
SELECT * FROM system.clusters WHERE cluster = 'analytics_cluster';

— 显示所有集群
SHOW CLUSTERS;

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

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

— 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 分片键不可修改

— ❌ 不支持修改分片键
— 没有 ALTER 语法可以修改分布式表的分片键

为什么?

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

如果需要修改:

— 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):本地表的物理切分

— 在每个节点上创建本地表,指定分区键
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):分布式表的跨节点路由

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

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

— 查询:同时利用分片和分区裁剪
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):

<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. 本地表 + 分布式表:

— 本地表:按用户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 天数据
无过滤 ❌ 全分片 ❌ 无 全部分片的全部数据

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

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

// 轮询写入:绕过分布式表,自己控制去哪个节点
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 分片键自动路由。

// 正确写法:通过分布式表写入
// 连接任意一个节点,写入分布式表即可
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 一站式从入门到实战》系列文章。

赞(0)
未经允许不得转载:171主机测评 » ClickHouse Sharding 分片与 Partitioning 分区:区别、联系与生产实践
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址