笔记篇-ClickHouse列式储存数据库

click.png

ClickHouse背景介绍

ClickHouse是俄罗斯第一大搜索引擎Yandex开发的列式储存数据库.令人惊喜的是,这个列式储存数据库的性能大幅超越了很多商业MPP数据库软件,比如Vertica,InfiniDB

ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。在传统的行式数据库系统中,数据按如下顺序存储:

Row WatchID JavaEnable Title GoodEvent EventTime
#0 89354350662 1 Investor Relations 1 2016-05-18 05:19:20
#1 90329509958 0 Contact us 1 2016-05-18 08:10:20
#2 89953706054 1 Mission 1 2016-05-18 07:38:00
#N

处于同一行中的数据总是被物理的存储在一起。常见的行式数据库系统有:MySQLPostgresMS SQL Server

在列式数据库系统中,数据按如下的顺序存储:

Row: #0 #1 #2 #N
WatchID: 89354350662 90329509958 89953706054
JavaEnable: 1 0 1
Title: Investor Relations Contact us Mission
GoodEvent: 1 1 1
EventTime: 2016-05-18 05:19:20 2016-05-18 08:10:20 2016-05-18 07:38:00

这些示例只显示了数据的排列顺序。来自不同列的值被单独存储,来自同一列的数据被存储在一起
常见的列式数据库有: **Vertica、 Paraccel (Actian Matrix,Amazon Redshift)、 Sybase IQ、 Exasol、 Infobright、 InfiniDB、 MonetDB (VectorWise, Actian Vector)、 LucidDB、 SAP HANA、 Google Dremel、 Google PowerDrill、 Druid、 kdb+**。

不同的数据存储方式适用不同的业务场景,数据访问的场景包括:进行了何种查询、多久查询一次以及各类查询的比例;每种类型的查询(行、列和字节)读取多少数据;读取数据和更新之间的关系;使用的数据集大小以及如何使用本地的数据集;是否使用事务,以及它们是如何进行隔离的;数据的复制机制与数据的完整性要求;每种类型的查询要求的延迟与吞吐量等等

系统负载越高,依据使用场景进行定制化就越重要,并且定制将会变的越精细。没有一个系统能够同时适用所有不同的业务场景。如果系统适用于广泛的场景,在负载高的情况下,要兼顾所有的场景,那么将不得不做出选择。是要平衡还是要效率?

ClickHouse相关特性

真正的列式数据库管理系统

在一个真正的列式数据库管理系统中,除了数据本身外不应该存在其他额外的数据。这意味着为了避免在值旁边存储它们的长度«number»,你必须支持固定长度数值类型。例如,10亿个UInt8类型的数据在未压缩的情况下大约消耗1GB左右的空间,如果不是这样的话,这将对CPU的使用产生强烈影响。即使是在未压缩的情况下,紧凑的存储数据也是非常重要的,因为解压缩的速度主要取决于未压缩数据的大小。
这是非常值得注意的,因为在一些其他系统中也可以将不同的列分别进行存储,但由于对其他场景进行的优化,使其无法有效的处理分析查询。例如: HBase,BigTable,Cassandra,HyperTable。在这些系统中,你可以得到每秒数十万的吞吐能力,但是无法得到每秒几亿行的吞吐能力。
需要说明的是,ClickHouse不单单是一个数据库, 它是一个数据库管理系统。因为它允许在运行时创建表和数据库、加载数据和运行查询,而无需重新配置或重启服务。

数据压缩

在一些列式数据库管理系统中(例如:InfiniDB CE 和 MonetDB) 并没有使用数据压缩。但是, 若想达到比较优异的性能,数据压缩确实起到了至关重要的作用。
除了在磁盘空间和CPU消耗之间进行不同权衡的高效通用压缩编解码器之外,ClickHouse还提供针对特定类型数据的专用编解码器,这使得ClickHouse能够与更小的数据库(如时间序列数据库)竞争并超越它们。

数据的磁盘存储

许多的列式数据库(如 SAP HANA, Google PowerDrill)只能在内存中工作,这种方式会造成比实际更多的设备预算。
ClickHouse被设计用于工作在传统磁盘上的系统,它提供每GB更低的存储成本,但如果可以使用SSD和内存,它也会合理的利用这些资源。

多核心并行处理

ClickHouse会使用服务器上一切可用的资源,从而以最自然的方式并行处理大型查询。

多服务器分布式处理

上面提到的列式数据库管理系统中,几乎没有一个支持分布式的查询处理。
在ClickHouse中,数据可以保存在不同的shard上,每一个shard都由一组用于容错的replica组成,查询可以并行地在所有shard上进行处理。这些对用户来说是透明的

支持SQL

ClickHouse支持一种基于SQL的声明式查询语言,它在许多情况下与ANSI SQL标准相同。
支持的查询GROUP BY, ORDER BY, FROM, JOIN, IN以及非相关子查询。
相关(依赖性)子查询和窗口函数暂不受支持,但将来会被实现。

向量引擎

为了高效的使用CPU,数据不仅仅按列存储,同时还按向量(列的一部分)进行处理,这样可以更加高效地使用CPU。

实时的数据更新

ClickHouse支持在表中定义主键。为了使查询能够快速在主键中进行范围查找,数据总是以增量的方式有序的存储在MergeTree中。因此,数据可以持续不断地高效的写入到表中,并且写入的过程中不会存在任何加锁的行为。

索引

按照主键对数据进行排序,这将帮助ClickHouse在几十毫秒以内完成对数据特定值或范围的查找。

适合在线查询

在线查询意味着在没有对数据做任何预处理的情况下以极低的延迟处理查询并将结果加载到用户的页面中。

支持近似计算

ClickHouse提供各种各样在允许牺牲数据精度的情况下对查询进行加速的方法:

  1. 用于近似计算的各类聚合函数,如:distinct values, medians, quantiles
  2. 基于数据的部分样本进行近似查询。这时,仅会从磁盘检索少部分比例的数据。
  3. 不使用全部的聚合条件,通过随机选择有限个数据聚合条件进行聚合。这在数据聚合条件满足某些分布条件下,在提供相当准确的聚合结果的同时降低了计算资源的使用。

Adaptive Join Algorithm

ClickHouse支持自定义JOIN多个表,它更倾向于散列连接算法,如果有多个大表,则使用合并-连接算法

支持数据复制和数据完整性

ClickHouse使用异步的多主复制技术。当数据被写入任何一个可用副本后,系统会在后台将数据分发给其他副本,以保证系统在不同副本上保持相同的数据。在大多数情况下ClickHouse能在故障后自动恢复,在一些少数的复杂情况下需要手动恢复。
更多信息,参见 数据复制

角色的访问控制

ClickHouse使用SQL查询实现用户帐户管理,并允许角色的访问控制,类似于ANSI SQL标准和流行的关系数据库管理系统。

限制

  1. 没有完整的事务支持。
  2. 缺少高频率,低延迟的修改或删除已存在数据的能力。仅能用于批量删除或修改数据,但这符合 GDPR
  3. 稀疏索引使得ClickHouse不适合通过其键检索单行的点查询。

OLAP 联机分析处理

联机分析处理(Online Analytical Processing)

  • 绝大多数是读请求
  • 数据以相当大的批次(> 1000行)更新,而不是单行更新;或者根本没有更新。
  • 已添加到数据库的数据不能修改。
  • 对于读取,从数据库中提取相当多的行,但只提取列的一小部分。
  • 宽表,即每个表包含着大量的列
  • 查询相对较少(通常每台服务器每秒查询数百次或更少)
  • 对于简单查询,允许延迟大约50毫秒
  • 列中的数据相对较小:数字和短字符串(例如,每个URL 60个字节)
  • 处理单个查询时需要高吞吐量(每台服务器每秒可达数十亿行)
  • 事务不是必须的
  • 对数据一致性要求低
  • 每个查询有一个大表。除了他以外,其他的都很小。
  • 查询结果明显小于源数据。换句话说,数据经过过滤或聚合,因此结果适合于单个服务器的RAM中

很容易可以看出,OLAP场景与其他通常业务场景(例如,OLTP或K/V)有很大的不同, 因此想要使用OLTP或Key-Value数据库去高效的处理分析查询场景,并不是非常完美的适用方案。例如,使用OLAP数据库去处理分析请求通常要优于使用MongoDB或Redis去处理分析请求。

ClickHouse适用场景

列式数据库更适合OLAP场景的原因

列式数据库更适合于OLAP场景(对于大多数查询而言,处理速度至少提高了100倍),下面详细解释了原因(通过图片更有利于直观理解):
行式
row-oriented.gif
列式
column-oriented.gif
看到差别了么?下面将详细介绍为什么会发生这种情况。

输入/输出

  1. 针对分析类查询,通常只需要读取表的一小部分列。在列式数据库中你可以只读取你需要的数据。例如,如果只需要读取100列中的5列,这将帮助你最少减少20倍的I/O消耗。
  2. 由于数据总是打包成批量读取的,所以压缩是非常容易的。同时数据按列分别存储这也更容易压缩。这进一步降低了I/O的体积。
  3. 由于I/O的降低,这将帮助更多的数据被系统缓存。

例如,查询«统计每个广告平台的记录数量»需要读取«广告平台ID»这一列,它在未压缩的情况下需要1个字节进行存储。如果大部分流量不是来自广告平台,那么这一列至少可以以十倍的压缩率被压缩。当采用快速压缩算法,它的解压速度最少在十亿字节(未压缩数据)每秒。换句话说,这个查询可以在单个服务器上以每秒大约几十亿行的速度进行处理。这实际上是当前实现的速度。

CPU

由于执行一个查询需要处理大量的行,因此在整个向量上执行所有操作将比在每一行上执行所有操作更加高效。同时这将有助于实现一个几乎没有调用成本的查询引擎。如果你不这样做,使用任何一个机械硬盘,查询引擎都不可避免的停止CPU进行等待。所以,在数据按列存储并且按列执行是很有意义的。
有两种方法可以做到这一点:

  1. 向量引擎:所有的操作都是为向量而不是为单个值编写的。这意味着多个操作之间的不再需要频繁的调用,并且调用的成本基本可以忽略不计。操作代码包含一个优化的内部循环。
  2. 代码生成:生成一段代码,包含查询中的所有操作。

这是不应该在一个通用数据库中实现的,因为这在运行简单查询时是没有意义的。但是也有例外,例如,MemSQL使用代码生成来减少处理SQL查询的延迟(只是为了比较,分析型数据库通常需要优化的是吞吐而不是延迟)。
请注意,为了提高CPU效率,查询语言必须是声明型的(SQL或MDX), 或者至少一个向量(J,K)。 查询应该只包含隐式循环,允许进行优化。

ClickHouse安装步骤

1
2
3
4
5
6
7
8
9
10
11
12
#安装工具
sudo yum install yum-utils
#导入
sudo rpm --import https://repo.clickhouse.tech/CLICKHOUSE-KEY.GPG
#设置仓库
sudo yum-config-manager --add-repo https://repo.clickhouse.tech/rpm/clickhouse.repo
#安装
sudo yum install clickhouse-server clickhouse-client
#启动
sudo /etc/init.d/clickhouse-server start
#进入客户端
clickhouse-client

ClickHouse简单交互

交互模式:

1
clickhouse-client clickhouse-client --host=... --port=... --user=... --password=... 

启用多行查询:

1
2
clickhouse-client -m 
clickhouse-client --multiline

以批处理模式运行查询:

1
2
3
clickhouse-client --query='SELECT 1' 
echo 'SELECT 1' | clickhouse-client
clickhouse-client <<< 'SELECT 1'

从指定格式的文件中插入数据:

1
2
clickhouse-client --query='INSERT INTO table VALUES' < data.txt 
clickhouse-client --query='INSERT INTO table FORMAT TabSeparated' < data.tsv

ClickHouse导入数据

1.下载数据

1
2
curl https://datasets.clickhouse.tech/hits/tsv/hits_v1.tsv.xz | unxz --threads=`nproc` > hits_v1.tsv
curl https://datasets.clickhouse.tech/visits/tsv/visits_v1.tsv.xz | unxz --threads=`nproc` > visits_v1.tsv

2.创建数据库

1
clickhouse-client --query "CREATE DATABASE IF NOT EXISTS tutorial"

3.创建数据表hits_v1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
CREATE TABLE tutorial.hits_v1 \
( \
`WatchID` UInt64, \
`JavaEnable` UInt8, \
`Title` String, \
`GoodEvent` Int16, \
`EventTime` DateTime, \
`EventDate` Date, \
`CounterID` UInt32, \
`ClientIP` UInt32, \
`ClientIP6` FixedString(16), \
`RegionID` UInt32, \
`UserID` UInt64, \
`CounterClass` Int8, \
`OS` UInt8, \
`UserAgent` UInt8, \
`URL` String, \
`Referer` String, \
`URLDomain` String, \
`RefererDomain` String, \
`Refresh` UInt8, \
`IsRobot` UInt8, \
`RefererCategories` Array(UInt16), \
`URLCategories` Array(UInt16), \
`URLRegions` Array(UInt32), \
`RefererRegions` Array(UInt32), \
`ResolutionWidth` UInt16, \
`ResolutionHeight` UInt16, \
`ResolutionDepth` UInt8, \
`FlashMajor` UInt8, \
`FlashMinor` UInt8, \
`FlashMinor2` String, \
`NetMajor` UInt8, \
`NetMinor` UInt8, \
`UserAgentMajor` UInt16, \
`UserAgentMinor` FixedString(2), \
`CookieEnable` UInt8, \
`JavascriptEnable` UInt8, \
`IsMobile` UInt8, \
`MobilePhone` UInt8, \
`MobilePhoneModel` String, \
`Params` String, \
`IPNetworkID` UInt32, \
`TraficSourceID` Int8, \
`SearchEngineID` UInt16, \
`SearchPhrase` String, \
`AdvEngineID` UInt8, \
`IsArtifical` UInt8, \
`WindowClientWidth` UInt16, \
`WindowClientHeight` UInt16, \
`ClientTimeZone` Int16, \
`ClientEventTime` DateTime, \
`SilverlightVersion1` UInt8, \
`SilverlightVersion2` UInt8, \
`SilverlightVersion3` UInt32, \
`SilverlightVersion4` UInt16, \
`PageCharset` String, \
`CodeVersion` UInt32, \
`IsLink` UInt8, \
`IsDownload` UInt8, \
`IsNotBounce` UInt8, \
`FUniqID` UInt64, \
`HID` UInt32, \
`IsOldCounter` UInt8, \
`IsEvent` UInt8, \
`IsParameter` UInt8, \
`DontCountHits` UInt8, \
`WithHash` UInt8, \
`HitColor` FixedString(1), \
`UTCEventTime` DateTime, \
`Age` UInt8, \
`Sex` UInt8, \
`Income` UInt8, \
`Interests` UInt16, \
`Robotness` UInt8, \
`GeneralInterests` Array(UInt16), \
`RemoteIP` UInt32, \
`RemoteIP6` FixedString(16), \
`WindowName` Int32, \
`OpenerName` Int32, \
`HistoryLength` Int16, \
`BrowserLanguage` FixedString(2), \
`BrowserCountry` FixedString(2), \
`SocialNetwork` String, \
`SocialAction` String, \
`HTTPError` UInt16, \
`SendTiming` Int32, \
`DNSTiming` Int32, \
`ConnectTiming` Int32, \
`ResponseStartTiming` Int32, \
`ResponseEndTiming` Int32, \
`FetchTiming` Int32, \
`RedirectTiming` Int32, \
`DOMInteractiveTiming` Int32, \
`DOMContentLoadedTiming` Int32, \
`DOMCompleteTiming` Int32, \
`LoadEventStartTiming` Int32, \
`LoadEventEndTiming` Int32, \
`NSToDOMContentLoadedTiming` Int32, \
`FirstPaintTiming` Int32, \
`RedirectCount` Int8, \
`SocialSourceNetworkID` UInt8, \
`SocialSourcePage` String, \
`ParamPrice` Int64, \
`ParamOrderID` String, \
`ParamCurrency` FixedString(3), \
`ParamCurrencyID` UInt16, \
`GoalsReached` Array(UInt32), \
`OpenstatServiceName` String, \
`OpenstatCampaignID` String, \
`OpenstatAdID` String, \
`OpenstatSourceID` String, \
`UTMSource` String, \
`UTMMedium` String, \
`UTMCampaign` String, \
`UTMContent` String, \
`UTMTerm` String, \
`FromTag` String, \
`HasGCLID` UInt8, \
`RefererHash` UInt64, \
`URLHash` UInt64, \
`CLID` UInt32, \
`YCLID` UInt64, \
`ShareService` String, \
`ShareURL` String, \
`ShareTitle` String, \
`ParsedParams` Nested( \
Key1 String, \
Key2 String, \
Key3 String, \
Key4 String, \
Key5 String, \
ValueDouble Float64), \
`IslandID` FixedString(16), \
`RequestNum` UInt32, \
`RequestTry` UInt8 \
) \
ENGINE = MergeTree() \
PARTITION BY toYYYYMM(EventDate) \
ORDER BY (CounterID, EventDate, intHash32(UserID)) \
SAMPLE BY intHash32(UserID)

4.创建数据库表visits_v1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
CREATE TABLE tutorial.visits_v1 \
( \
`CounterID` UInt32, \
`StartDate` Date, \
`Sign` Int8, \
`IsNew` UInt8, \
`VisitID` UInt64, \
`UserID` UInt64, \
`StartTime` DateTime, \
`Duration` UInt32, \
`UTCStartTime` DateTime, \
`PageViews` Int32, \
`Hits` Int32, \
`IsBounce` UInt8, \
`Referer` String, \
`StartURL` String, \
`RefererDomain` String, \
`StartURLDomain` String, \
`EndURL` String, \
`LinkURL` String, \
`IsDownload` UInt8, \
`TraficSourceID` Int8, \
`SearchEngineID` UInt16, \
`SearchPhrase` String, \
`AdvEngineID` UInt8, \
`PlaceID` Int32, \
`RefererCategories` Array(UInt16), \
`URLCategories` Array(UInt16), \
`URLRegions` Array(UInt32), \
`RefererRegions` Array(UInt32), \
`IsYandex` UInt8, \
`GoalReachesDepth` Int32, \
`GoalReachesURL` Int32, \
`GoalReachesAny` Int32, \
`SocialSourceNetworkID` UInt8, \
`SocialSourcePage` String, \
`MobilePhoneModel` String, \
`ClientEventTime` DateTime, \
`RegionID` UInt32, \
`ClientIP` UInt32, \
`ClientIP6` FixedString(16), \
`RemoteIP` UInt32, \
`RemoteIP6` FixedString(16), \
`IPNetworkID` UInt32, \
`SilverlightVersion3` UInt32, \
`CodeVersion` UInt32, \
`ResolutionWidth` UInt16, \
`ResolutionHeight` UInt16, \
`UserAgentMajor` UInt16, \
`UserAgentMinor` UInt16, \
`WindowClientWidth` UInt16, \
`WindowClientHeight` UInt16, \
`SilverlightVersion2` UInt8, \
`SilverlightVersion4` UInt16, \
`FlashVersion3` UInt16, \
`FlashVersion4` UInt16, \
`ClientTimeZone` Int16, \
`OS` UInt8, \
`UserAgent` UInt8, \
`ResolutionDepth` UInt8, \
`FlashMajor` UInt8, \
`FlashMinor` UInt8, \
`NetMajor` UInt8, \
`NetMinor` UInt8, \
`MobilePhone` UInt8, \
`SilverlightVersion1` UInt8, \
`Age` UInt8, \
`Sex` UInt8, \
`Income` UInt8, \
`JavaEnable` UInt8, \
`CookieEnable` UInt8, \
`JavascriptEnable` UInt8, \
`IsMobile` UInt8, \
`BrowserLanguage` UInt16, \
`BrowserCountry` UInt16, \
`Interests` UInt16, \
`Robotness` UInt8, \
`GeneralInterests` Array(UInt16), \
`Params` Array(String), \
`Goals` Nested( \
ID UInt32, \
Serial UInt32, \
EventTime DateTime, \
Price Int64, \
OrderID String, \
CurrencyID UInt32), \
`WatchIDs` Array(UInt64), \
`ParamSumPrice` Int64, \
`ParamCurrency` FixedString(3), \
`ParamCurrencyID` UInt16, \
`ClickLogID` UInt64, \
`ClickEventID` Int32, \
`ClickGoodEvent` Int32, \
`ClickEventTime` DateTime, \
`ClickPriorityID` Int32, \
`ClickPhraseID` Int32, \
`ClickPageID` Int32, \
`ClickPlaceID` Int32, \
`ClickTypeID` Int32, \
`ClickResourceID` Int32, \
`ClickCost` UInt32, \
`ClickClientIP` UInt32, \
`ClickDomainID` UInt32, \
`ClickURL` String, \
`ClickAttempt` UInt8, \
`ClickOrderID` UInt32, \
`ClickBannerID` UInt32, \
`ClickMarketCategoryID` UInt32, \
`ClickMarketPP` UInt32, \
`ClickMarketCategoryName` String, \
`ClickMarketPPName` String, \
`ClickAWAPSCampaignName` String, \
`ClickPageName` String, \
`ClickTargetType` UInt16, \
`ClickTargetPhraseID` UInt64, \
`ClickContextType` UInt8, \
`ClickSelectType` Int8, \
`ClickOptions` String, \
`ClickGroupBannerID` Int32, \
`OpenstatServiceName` String, \
`OpenstatCampaignID` String, \
`OpenstatAdID` String, \
`OpenstatSourceID` String, \
`UTMSource` String, \
`UTMMedium` String, \
`UTMCampaign` String, \
`UTMContent` String, \
`UTMTerm` String, \
`FromTag` String, \
`HasGCLID` UInt8, \
`FirstVisit` DateTime, \
`PredLastVisit` Date, \
`LastVisit` Date, \
`TotalVisits` UInt32, \
`TraficSource` Nested( \
ID Int8, \
SearchEngineID UInt16, \
AdvEngineID UInt8, \
PlaceID UInt16, \
SocialSourceNetworkID UInt8, \
Domain String, \
SearchPhrase String, \
SocialSourcePage String), \
`Attendance` FixedString(16), \
`CLID` UInt32, \
`YCLID` UInt64, \
`NormalizedRefererHash` UInt64, \
`SearchPhraseHash` UInt64, \
`RefererDomainHash` UInt64, \
`NormalizedStartURLHash` UInt64, \
`StartURLDomainHash` UInt64, \
`NormalizedEndURLHash` UInt64, \
`TopLevelDomain` UInt64, \
`URLScheme` UInt64, \
`OpenstatServiceNameHash` UInt64, \
`OpenstatCampaignIDHash` UInt64, \
`OpenstatAdIDHash` UInt64, \
`OpenstatSourceIDHash` UInt64, \
`UTMSourceHash` UInt64, \
`UTMMediumHash` UInt64, \
`UTMCampaignHash` UInt64, \
`UTMContentHash` UInt64, \
`UTMTermHash` UInt64, \
`FromHash` UInt64, \
`WebVisorEnabled` UInt8, \
`WebVisorActivity` UInt32, \
`ParsedParams` Nested( \
Key1 String, \
Key2 String, \
Key3 String, \
Key4 String, \
Key5 String, \
ValueDouble Float64), \
`Market` Nested( \
Type UInt8, \
GoalID UInt32, \
OrderID String, \
OrderPrice Int64, \
PP UInt32, \
DirectPlaceID UInt32, \
DirectOrderID UInt32, \
DirectBannerID UInt32, \
GoodID String, \
GoodName String, \
GoodQuantity Int32, \
GoodPrice Int64), \
`IslandID` FixedString(16) \
) \
ENGINE = CollapsingMergeTree(Sign) \
PARTITION BY toYYYYMM(StartDate) \
ORDER BY (CounterID, StartDate, intHash32(UserID), VisitID) \
SAMPLE BY intHash32(UserID)

5.导入数据

1
2
clickhouse-client --query "INSERT INTO tutorial.hits_v1 FORMAT TSV" --max_insert_block_size=100000 < hits_v1.tsv
clickhouse-client --query "INSERT INTO tutorial.visits_v1 FORMAT TSV" --max_insert_block_size=100000 < visits_v1.tsv

6.查看是否导入成功

1
2
clickhouse-client --query "SELECT COUNT(*) FROM tutorial.hits_v1"
clickhouse-client --query "SELECT COUNT(*) FROM tutorial.visits_v1"

ClickHouse集群部署

ClickHouse集群是一个同质集群。 设置步骤:

  1. 在群集的所有机器上安装ClickHouse服务端
  2. 在配置文件中设置群集配置
  3. 在每个实例上创建本地表
  4. 创建一个分布式表

分布式表实际上是一种view,映射到ClickHouse集群的本地表。 从分布式表中执行SELECT查询会使用集群所有分片的资源。 您可以为多个集群指定configs,并创建多个分布式表,为不同的集群提供视图。
具有三个分片,每个分片一个副本的集群的示例配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<remote_servers>
<perftest_3shards_1replicas>
<shard>
<replica>
<host>example-perftest01j.yandex.ru</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>example-perftest02j.yandex.ru</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>example-perftest03j.yandex.ru</host>
<port>9000</port>
</replica>
</shard>
</perftest_3shards_1replicas>
</remote_servers>

为了进一步演示,让我们使用和创建hits_v1表相同的CREATE TABLE语句创建一个新的本地表,但表名不同:

1
CREATE TABLE tutorial.hits_local (...) ENGINE = MergeTree() ... 

创建提供集群本地表视图的分布式表:

1
CREATE TABLE tutorial.hits_all AS tutorial.hits_local ENGINE = Distributed(perftest_3shards_1replicas, tutorial, hits_local, rand()); 

常见的做法是在集群的所有计算机上创建类似的分布式表。 它允许在群集的任何计算机上运行分布式查询。 还有一个替代选项可以使用以下方法为给定的SELECT查询创建临时分布式表远程表功能。
让我们运行INSERT SELECT将该表传播到多个服务器。

1
INSERT INTO tutorial.hits_all SELECT * FROM tutorial.hits_v1; 

注意:
这种方法不适合大型表的分片。 有一个单独的工具 clickhouse-copier 这可以重新分片任意大表。
正如您所期望的那样,如果计算量大的查询使用3台服务器而不是一个,则运行速度快N倍。
在这种情况下,我们使用了具有3个分片的集群,每个分片都包含一个副本。
为了在生产环境中提供弹性,我们建议每个分片应包含分布在多个可用区或数据中心(或至少机架)之间的2-3个副本。 请注意,ClickHouse支持无限数量的副本。
包含三个副本的一个分片集群的示例配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<remote_servers>
...
<perftest_1shards_3replicas>
<shard>
<replica>
<host>example-perftest01j.yandex.ru</host>
<port>9000</port>
</replica>
<replica>
<host>example-perftest02j.yandex.ru</host>
<port>9000</port>
</replica>
<replica>
<host>example-perftest03j.yandex.ru</host>
<port>9000</port>
</replica>
</shard>
</perftest_1shards_3replicas>
</remote_servers>

启用本机复制Zookeeper是必需的。 ClickHouse负责所有副本的数据一致性,并在失败后自动运行恢复过程。建议将ZooKeeper集群部署在单独的服务器上(其中没有其他进程,包括运行的ClickHouse)。

注意: ZooKeeper不是一个严格的要求:在某些简单的情况下,您可以通过将数据写入应用程序代码中的所有副本来复制数据。 这种方法是不建议的,在这种情况下,ClickHouse将无法保证所有副本上的数据一致性。 因此需要由您的应用来保证这一点。

ZooKeeper位置在配置文件中指定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<zookeeper>
<node>
<host>zoo01.yandex.ru</host>
<port>2181</port>
</node>
<node>
<host>zoo02.yandex.ru</host>
<port>2181</port>
</node>
<node>
<host>zoo03.yandex.ru</host>
<port>2181</port>
</node>
</zookeeper>

此外,我们需要设置宏来识别每个用于创建表的分片和副本:

1
2
3
4
<macros>
<shard>01</shard>
<replica>01</replica>
</macros>

如果在创建复制表时没有副本,则会实例化新的第一个副本。 如果已有实时副本,则新副本将克隆现有副本中的数据。 您可以选择首先创建所有复制的表,然后向其中插入数据。 另一种选择是创建一些副本,并在数据插入之后或期间添加其他副本。

1
CREATE TABLE tutorial.hits_replica (...) ENGINE = ReplcatedMergeTree(     '/clickhouse_perftest/tables/{shard}/hits',     '{replica}' ) ... 

在这里,我们使用ReplicatedMergeTree表引擎。 在参数中,我们指定包含分片和副本标识符的ZooKeeper路径。

1
INSERT INTO tutorial.hits_replica SELECT * FROM tutorial.hits_local; 

复制在多主机模式下运行。数据可以加载到任何副本中,然后系统自动将其与其他实例同步。复制是异步的,因此在给定时刻,并非所有副本都可能包含最近插入的数据。至少应该有一个副本允许数据摄入。另一些则会在重新激活后同步数据并修复一致性。请注意,这种方法允许最近插入的数据丢失的可能性很低。

ClickHouse 可 视 化

DataGrip 是JetBrains的数据库IDE,专门支持ClickHouse。 它还嵌入到其他基于IntelliJ的工具中:PyCharm,IntelliJ IDEA,GoLand,PhpStorm等。
特征:

  • 非常快速的代码完成。
  • ClickHouse语法高亮显示。
  • 支持ClickHouse特有的功能,例如嵌套列,表引擎。
  • 数据编辑器。
  • 重构。
  • 搜索和导航。

ClickHouse参考资料

官网:https://clickhouse.tech/
ClickHouse 教程:https://clickhouse.tech/docs/en/getting-started/tutorial/
GitHub:https://github.com/ClickHouse/ClickHouse


笔记篇-ClickHouse列式储存数据库
https://mikeygithub.github.io/2021/06/20/yuque/数据库篇-ClickHouse列式储存数据库/
作者
Mikey
发布于
2021年6月20日
许可协议