ClickHouse分区表的正面与侧面

news2024/12/26 2:09:26

当我们处理连续数据并需要基于移动窗口(如,仅使用过去三个月数据)计算时使用分区功能非常有用,因为分区无需删除数据,就能高效避过不使用的(或过期)数据。本文介绍分区表原理,对比查询、插入性能,了解分区的优势于劣势,从而理解在恰当的应用场景使用分区功能。

分区表原理

ClickHouse分区表把表分成多个块,从而后续可以高效地处理这些块(如,删除或移动),要定义分区表,需要使用PARTITION BY表达式指定分区键,示例如下:

CREATE TABLE test (
    `Days` Date, 
    `Name` String,
    `Event` String
) engine=MergeTree()
PARTITION BY (Days) ORDER BY (Name);

通过下面语句查看元信息:

select table ,engine,partition_key,sorting_key,primary_key
from system.tables where table = 'test';

返回表主要字段信息:


┌─table─┬─engine────┬─partition_key─┬─sorting_key─┬─primary_key─┐
│ test  │ MergeTree │ Days          │ Name        │ Name        │
└───────┴───────────┴───────────────┴─────────────┴─────────────┘

当定义了分区键,ClickHouse自动路由存入数据至对应块(分区):

在这里插入图片描述

用户看到的仍为单个表,与非分区表一样。但内部表数据被存储在多个分区,下面详细说明。
在这里插入图片描述

分区表测试

下面针对相同场景分别定义分区表和非分区表,然后对比读写性能。

非分区表

假设场景需要存储时间序列数据,我们仅需要过去7天数据,每天自动清除过期数据。创建表如下:

CREATE TABLE unpartitioned
(`t` Date, `label` UInt8, `value` UInt32 )
ENGINE = MergeTree ORDER BY (label, t);

通常需要基于t和label 或仅label进行过滤:

SELECT AVG(value) FROM unpartitioned WHERE t = today() AND label = 10

现在填充1亿行测试数据:

INSERT INTO unpartitioned
SELECT today() - rand32() % 10, rand32() % 10000, rand32()
FROM numbers(100000000);

分区表

下面创建分区表:

CREATE TABLE partitioned
(`t` Date, `label` UInt8, `value` UInt32)
ENGINE = MergeTree PARTITION BY t ORDER BY label;

INSERT INTO partitioned SELECT * FROM unpartitioned;

因为使用了t自动作为分区键,则排序字段中就不再需要。下面查看分区的分区情况:

SELECT partition, formatReadableSize(sum(bytes))
FROM system.parts
WHERE table = 'partitioned'
GROUP BY partition

返回结果:

SELECT
    partition,
    formatReadableSize(sum(bytes))
FROM system.parts
WHERE table = 'partitioned'
GROUP BY partition

Query id: 52db4a34-0640-4fa7-b7a7-49085606ca62

┌─partition──┬─formatReadableSize(sum(bytes_on_disk))─┐
│ 2023-04-12 │ 115.13 MiB                             │
│ 2023-04-19 │ 115.12 MiB                             │
│ 2023-04-17 │ 115.09 MiB                             │
│ 2023-04-14 │ 115.16 MiB                             │
│ 2023-04-11 │ 115.26 MiB                             │
│ 2023-04-15 │ 115.16 MiB                             │
│ 2023-04-10 │ 115.07 MiB                             │
│ 2023-04-18 │ 115.14 MiB                             │
│ 2023-04-13 │ 115.25 MiB                             │
│ 2023-04-16 │ 115.09 MiB                             │
└────────────┴────────────────────────────────────────┘

我们看到了10个分区(与期望一致),即每天创建了不同分区。

查询性能

针对分区表,ClickHouse会检查分区决定使用哪些分区或跳过哪些分区,性能优劣取决于使用了多少分区。

  • 使用单个分区

假设查询下面查询语句:

SELECT avg(value) FROM unpartitioned WHERE t = today() AND label = 10

返回结果:


SELECT avg(value)
FROM unpartitioned
WHERE (t = today()) AND (label = 10)

Query id: f36c9e74-5f6b-4a17-aee6-98ce65867a84

┌─────────avg(value)─┐
│ 2148085697.0056076 │
└────────────────────┘

1 rows in set. Elapsed: 0.031 sec. Processed 122.88 thousand rows, 860.16 KB (3.91 million rows/s., 27.36 MB/s.)

处理了122.88k行,下面查询分区表:

SELECT avg(value)
FROM partitioned
WHERE (t = today()) AND (label = 10)

Query id: 0f4b4a30-c565-4370-b7bc-05c135a35fb7

┌─────────avg(value)─┐
│ 2148085697.0056076 │
└────────────────────┘

1 rows in set. Elapsed: 0.038 sec. Processed 114.69 thousand rows, 802.82 KB (2.98 million rows/s., 20.89 MB/s.)

分区表需要扫描的行数较少,下面分析查询语句进行说明:

EXPLAIN ESTIMATE
SELECT avg(value)
FROM partitioned
WHERE (t = today()) AND (label = 10)

Query id: 23dff40c-b795-46a7-94bf-39018ff06976

┌─database─┬─table───────┬─parts─┬───rows─┬─marks─┐
│ default  │ partitioned │     4 │ 114688 │    14 │
└──────────┴─────────────┴───────┴────────┴───────┘

1 rows in set. Elapsed: 0.003 sec.


EXPLAIN ESTIMATE
SELECT avg(value)
FROM unpartitioned
WHERE (t = today()) AND (label = 10)

Query id: 4dd38904-52a3-4bed-999a-efe987fe172b

┌─database─┬─table─────────┬─parts─┬───rows─┬─marks─┐
│ default  │ unpartitioned │     5 │ 122880 │    15 │
└──────────┴───────────────┴───────┴────────┴───────┘

分区表选择4个分区,分区表仅有一个排序键label字段,ClickHouse首先发现目标分区,然后根据label字段进行过滤。查询条件中日期对应的分区一共有5个:

SELECT
    partition,
    name,
    part_type,
    formatReadableSize(sum(bytes))
FROM system.parts
WHERE (table = 'partitioned') AND (partition = '2023-04-19')
GROUP BY
    partition,
    name,
    part_type

Query id: 23d89ac3-ff43-4513-a7d0-bd03195b5144

┌─partition──┬─name───────────────┬─part_type─┬─formatReadableSize(sum(bytes_on_disk))─┐
│ 2023-04-19 │ 20230419_510_670_2 │ Wide      │ 6.80 MiB                               │
│ 2023-04-19 │ 20230419_5_496_3   │ Wide      │ 20.25 MiB                              │
│ 2023-04-19 │ 20230419_674_831_2 │ Wide      │ 6.89 MiB                               │
│ 2023-04-19 │ 20230419_905_954_1 │ Compact   │ 2.17 MiB                               │
│ 2023-04-19 │ 20230419_845_899_1 │ Compact   │ 2.43 MiB                               │
└────────────┴────────────────────┴───────────┴────────────────────────────────────────┘

在非分区表,ClickHouse基于排序键(label + t)载入所有可能的部分。

下面换个条件进行对比,跳过分区键:


EXPLAIN ESTIMATE
SELECT avg(value)
FROM partitioned
WHERE label = 10

Query id: eeea741c-e1f7-417b-8dc5-4df029831d49

┌─database─┬─table───────┬─parts─┬───rows─┬─marks─┐
│ default  │ partitioned │    38 │ 720896 │    88 │
└──────────┴─────────────┴───────┴────────┴───────┘

EXPLAIN ESTIMATE
SELECT avg(value)
FROM unpartitioned
WHERE label = 10

Query id: 2ac544a0-2880-44db-889b-3985fb17f61e

┌─database─┬─table─────────┬─parts─┬───rows─┬─marks─┐
│ default  │ unpartitioned │     5 │ 442368 │    54 │
└──────────┴───────────────┴───────┴────────┴───────┘

我们看到未分区表仍使用排序键进行过滤,而分区表首先查询相关分区,然后再过滤每个分区,性能显然不如非分区表。

这给我们关于分区表性能的重要启示:

  • 如果需要让大多数查询命中单个分区,通常会提高查询性能
  • 相反如果查询访问多个分区,则可能导致性能下降

分区会影响读性能,同样排序键也会影响性能。

写性能

使用分区也影响数据的写入方式。当数据到达分区表,ClickHouse首先为数据寻找相应的分区,然后填充(创建并填充)新数据。相比于非分区表这是额外的工作:

INSERT INTO partitioned SELECT
    today() - (rand32() % 100),
    rand32() % 100,
    rand32()
FROM numbers(250000000)

Query id: 8034ae4f-3542-4135-b3ab-714d6fbe17d7

Ok.

0 rows in set. Elapsed: 91.676 sec. Processed 250.60 million rows, 2.00 GB (2.73 million rows/s., 21.87 MB/s.)

INSERT INTO unpartitioned SELECT
    today() - (rand32() % 100),
    rand32() % 100,
    rand32()
FROM numbers(250000000)

Query id: 6922f79e-a920-4567-a47f-e432819f58d2

Ok.

0 rows in set. Elapsed: 47.080 sec. Processed 250.60 million rows, 2.00 GB (5.32 million rows/s., 42.58 MB/s.)

我们可以看到数据插入到分区表和非分区表(label作为排序键)的差异,分区表插入时间为91.676 sec,非分区表为47.080 sec。另外单个查询中插入分区数有一定限制,缺省为100,尝试插入太多分区会产生错误,这里测试SQL一次性包括1000个分区:


INSERT INTO partitioned SELECT
    today() - (rand32() % 1000),
    rand32() % 100,
    rand32()
FROM numbers(250000000)

Query id: 518892e3-9a45-486a-84a7-f05f35dce1d7


0 rows in set. Elapsed: 0.191 sec.

Received exception from server (version 22.2.2):
Code: 252. DB::Exception: Received from localhost:9000. DB::Exception: Too many partitions for single INSERT block (more than 100). The limit is controlled by 'max_partitions_per_insert_block' setting. Large number of partitions is a common misconception. It will lead to severe negative performance impact, including slow server startup, slow INSERT queries and slow SELECT queries. Recommended total number of partitions for a table is under 1000..10000. Please note, that partitioning is not intended to speed up SELECT queries (ORDER BY key is sufficient to make range queries fast). Partitions are intended for data manipulation (DROP PARTITION, etc).. (TOO_MANY_PARTS)

如果需要插入很多分区,不得不分割数据为多个批次,从而减少分区数量。考虑到这一点,当我们插入序列数据(例如时间序列)时,特别适合使用分区表,这样插入批次通常会涉及单个分区或少量分区。

删除分区数据

因为我们仅需要最近7天数据,对于未分区表,需要每天进行删除:

ALTER TABLE unpartitioned DELETE WHERE t < (today() - 7) 

该语句属于修改语句,数据行不能被立刻删除,仅被ClickHouse标记。ClickHouse后端程序会在恰当的时间进行永久删除。对于分区表,可以通过删除分区立刻删除数据:

ALTER TABLE partitioned DROP PARTITION '2023-04-17'

分区名(如2023-04-17)可以在system.parts表中找到。该操作属于轻量级操作(仅文件夹在磁盘上被删除),服务端几乎没有性能影响,也能立刻看到表占用空间大小减少。

总结

在Clickhouse中分区功能实现透明地将表拆分为多个块,并能够独立管理这些块(例如删除它们)。分区键应该始终为低基数表达式(如有几十个值)。不要仅为了提高查询性能而考虑分区,同时也要注意到分区表数据写入性能可能会降低。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/437525.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文读懂|数字化到底是什么?

现在大家都在说数字化&#xff0c;数字化到底是什么&#xff1f; 翻阅很多关于数字化的文章&#xff0c;大部分都在混淆术语&#xff0c;部分文章已经开始将数字化标记为数字化转型&#xff0c;以安抚管理层、获得项目批准或进行销售...... 所以这篇内容&#xff0c;我会尽可…

第十四章 动手,高级写法应用让SQL飞

参考《收获&#xff0c;不止SQL优化》作者: 梁敬彬 / 梁敬弘 所谓高级SQL&#xff0c;就是内部被优化过的SQL&#xff0c;可以用简单的语法实现复杂的功能&#xff0c;同时性能上还有提升。比如INSERT ALL、WITH子句、MERGE…… 一、 高级写法思维导图&#xff08;自己整理&am…

java连接webservice

前言 WebService 也叫XML Web Service&#xff0c;WebService是一种可以接收从Internet或者Intranet上的其它系统中传递过来的请求&#xff0c;轻量级的独立的通讯技术。是通过SOAP在Web上提供的软件服务&#xff0c;使用WSDL文件进行说明&#xff0c;并通过UDDI进行注册。 一、…

什么是 IT 基础架构管理

各行各业的企业组织不断面临创新和扩展的压力。就在十多年前&#xff0c;一个企业组织可以争取时间&#xff0c;在投资新技术方面保持保守&#xff0c;同时仍然保持竞争优势。快进到今天&#xff0c;随着业务实践的变化和新技术的不断涌现&#xff0c;商业和技术格局更加动态。…

【C++】文件IO流及stringstream流的使用

文章目录 一、引入二、自定义类型隐式类型转换三、sync_with_stdio同步四、文件IO流4.1 open和close文件4.2 写入文件与读出文件 五、stringstream流的使用5.1 将数值类型数据格式化为字符串5.2 序列化和反序列化 一、引入 int main() {string str;while (cin >> str){c…

适合零基础小白,循序渐进学习--文件操作--相关函数

目录 一. 前言 二. 正文 2. 1什么是文件 &#xff08;1&#xff09;程序文件 &#xff08;2&#xff09;数据文件 2.2文件名 2.3文件类型 2.4 文件缓冲区 2.5 文件指针 2.7 文件操作函数 (1) fopen()--- 打开文件 (2) fclose()--- 关闭文件 2.7 文件的顺序读写&…

ArcGIS制图技巧

目录 1、经纬度网置于底层 2、设置经纬网刻度为英文 3、设置左右经纬度为垂直&#xff0c;不显示分秒&#xff0c;以及改变字体等 4、拖动制图中的图层 5、设置经纬度网为曲线 6、根据经纬度导入样本点 1、经纬度网置于底层 最初我们的网格是下图这个样子&#xff1a; 然…

智慧化工业企业能耗管理系统平台的构建

节能监测技术是节能减排、有效节能的重要支撑技术&#xff0c;当前能源资源日渐枯竭的紧张形势下&#xff0c;节能意识的增强以及各行业对节能技术的需求不断增加&#xff0c;使得节能技术的开发越来越受到人们的重视。工业企业能耗监测系统的构建是按照“统一规划、分期分批”…

头像展示样式

先上效果图 再上代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><style>body {align-items: center;background-color: #f2f2f2;display: flex;font-fa…

代码随想录算法训练营第三十天| 332.重新安排行程、回溯总结

文章目录 332.重新安排行程:star:回溯总结:star:1.组合问题2.切割问题3.子集问题4.排列问题5.棋盘问题(未完待续)6.复杂度分析 332.重新安排行程⭐️ 链接:代码随想录 本题是一道困难题&#xff0c;其实困难点也就在容器的选择和使用上 结果集采用数组存取即可操作集要用一个m…

算法篇——栈与队列大集合(js版)

232.用栈实现队列 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; 实现 MyQueue 类&#xff1a; void push(int x) 将元素 x 推到队列的末尾 int pop() 从队列的开头移除并返回元素 int…

SpringBoot集成Redis及问题解决

SpringBoot集成Redis 此篇文章为SpringBoot集成Redis的简单介绍&#xff0c;依赖、序列化操作、工具类都可以在后面的实操中直接搬运使用或者在此基础上进行改进使用 1、集成Redis 1.1、新建SpringBoot项目 新建项目这边就不一一介绍了&#xff0c;大家如果还有不会的可以自行…

【LeetCode】144.二叉树的前序遍历

1.问题 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[] 示例 3&#xff1a; 输入&#xff1a;root [1]…

七大排序的时间复杂度和空间复杂度

文章目录 七大排序的分类时间复杂度空间复杂度稳定性直接插入排序希尔排序选择排序堆排序冒泡排序快速排序归并排序总结 七大排序的分类 时间复杂度 时间复杂度是指一个程序中基本语句被执行的次数,一般认为是最坏情况。 空间复杂度 空间复杂度是指在一个程序执行时要额外开…

美团DAT:A Dual Augmented Two-tower Model for Online Large-scale Recommendation

A Dual Augmented Two-tower Model for Online Large-scale Recommendation 美团的对偶增强双塔为了user塔和item塔操碎了心&#xff0c;众所周知&#xff0c;双塔的一个大毛病就是item和user的交叉太晚&#xff0c;重要的信息经过层层神经网络的抽象提取&#xff0c;有些重要…

15、异常处理

文章目录 1、错误处理1、默认规则2、定制错误处理逻辑3、异常处理自动配置原理4、异常处理步骤流程 【尚硅谷】SpringBoot2零基础入门教程-讲师&#xff1a;雷丰阳 笔记 路还在继续&#xff0c;梦还在期许 1、错误处理 1、默认规则 默认情况下&#xff0c;Spring Boot提供/er…

傻傻的分不清 Comparator 和 Comparable 接口? 两分钟弄懂~

目录 一、Comparable 接口 二、Comparator 接口 一、Comparable 接口 Comparable 是一个排序接口&#xff08;意味着该支持排序&#xff09;&#xff0c;可以看作内比较器&#xff0c;也就是说可以和自己比较&#xff0c;通常用来自己属性与自己属性进行比较&#xff0c;最后通…

百年孤独 -- 有感

《创世记》之后&#xff0c;首部值得全人类阅读的文学巨著。 — 纽约时报 加西亚 马尔克斯以小说作品创建了一个自己的世界&#xff0c;一个浓缩的宇宙&#xff0c;其中喧嚣纷乱却又生动可信的现实&#xff0c;映射了一篇大陆及其人民的富足与贫困。 — 诺贝尔文学奖颁奖辞 马…

wordpress+apache搭建问题总结

访问首页出现服务器目录 需要到apache的httpd.conf 添加index.php默认值 更新失败。 此响应不是合法的JSON响应 大多出现于修改固定连接后 打开httpd.conf文件,把AllowOverride None修改为AllowOverride All,重启apache即可解决 AllowOverride Noneyi意味着忽略.htaccess文件…

VS2022中创建C++SDK库(dll动态库)并调用SDK库(dll动态库)

VS2022中创建CSDK库(dll动态库)并调用SDK库(dll动态库) 一、说明 通过前两篇的文章我们知道了封装一个Qt下的SDK库&#xff08;dll动态链接库&#xff09;和封装Pimpl模式。 Qt创建SDK库(dll动态库)并调用SDK库(dll动态库) SDK(动态链接库dll)的封装技巧 本篇介绍在C VS下…