您好,登錄后才能下訂單哦!
這篇文章主要講解了“clickhouse基礎知識總結”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“clickhouse基礎知識總結”吧!
Clickhouse是一個用于聯機分析處理(OLAP)的列式數據庫管理系統(columnar DBMS)。
傳統數據庫在數據大小比較小,索引大小適合內存,數據緩存命中率足夠高的情形下能正常提供服務。但殘酷的是,這種理想情形最終會隨著業務的增長走到盡頭,查詢會變得越來越慢。你可能通過增加更多的內存,訂購更快的磁盤等等來解決問題(縱向擴展),但這只是拖延解決本質問題。如果你的需求是解決怎樣快速查詢出結果,那么ClickHouse也許可以解決你的問題。
應用場景:
1.
絕大多數請求都是用于讀訪問的
2.
數據需要以大批次(大于1000行)進行更新,而不是單行更新;或者根本沒有更新操作
3.
數據只是添加到數據庫,沒有必要修改
4.
讀取數據時,會從數據庫中提取出大量的行,但只用到一小部分列
5.
表很“寬”,即表中包含大量的列
6.
查詢頻率相對較低(通常每臺服務器每秒查詢數百次或更少)
7.
對于簡單查詢,允許大約50毫秒的延遲
8.
列的值是比較小的數值和短字符串(例如,每個URL只有60個字節)
9.
在處理單個查詢時需要高吞吐量(每臺服務器每秒高達數十億行)
10.
不需要事務
11.
數據一致性要求較低
12.
每次查詢中只會查詢一個大表。除了一個大表,其余都是小表
13.
查詢結果顯著小于數據源。即數據有過濾或聚合。返回結果不超過單個服務器內存大小
相應地,使用ClickHouse也有其本身的限制:
1.
不支持真正的刪除/更新支持 不支持事務(期待后續版本支持)
2.
不支持二級索引
3.
有限的SQL支持,join實現與眾不同
4.
不支持窗口功能
5.
元數據管理需要人工干預維護
-- 列出數據庫列表 show databases; -- 列出數據庫中表列表 show tables; -- 創建數據庫 create database test; -- 刪除一個表 drop table if exists test.t1; -- 創建第一個表 create /*temporary*/ table /*if not exists*/ test.m1 ( id UInt16 ,name String ) ENGINE = Memory ; -- 插入測試數據 insert into test.m1 (id, name) values (1, 'abc'), (2, 'bbbb'); -- 查詢 select * from test.m1;
默認值
默認值 的處理方面, ClickHouse 中,默認值總是有的,如果沒有顯示式指定的話,會按字段類型處理:
數字類型, 0
字符串,空字符串
數組,空數組
日期, 0000-00-00
時間, 0000-00-00 00:00:00
注:NULLs 是不支持的
數據類型
1.
整型:UInt8,UInt16,UInt32,UInt64,Int8,Int16,Int32,Int64
范圍U開頭-2N/2~2N-1;非U開頭0~2^N-1
2.
枚舉類型:Enum8,Enum16
Enum('hello'=1,'test'=-1),Enum是有符號的整型映射的,因此負數也是可以的
3.
字符串型:FixedString(N),String
N是最大字節數,不是字符長度,如果是UTF8字符串,那么就會占3個字節,GBK會占2字節;String可以用來替換VARCHAR,BLOB,CLOB等數據類型
4.
時間類型:Date
5.
數組類型:Array(T)
T是一個基本類型,包括arry在內,官方不建議使用多維數組
6.
元組:Tuple
7.
結構:Nested(name1 Type1,name2 Type2,...)
類似一種map的結
物化列
指定 MATERIALIZED 表達式,即將一個列作為物化列
處理了,這意味著這個列的值不能從insert
語句獲取,只能是自己計算出來的。同時,
物化列也不會出現在 select *
的結果中:
drop table if exists test.m2; create table test.m2 ( a MATERIALIZED (b+1) ,b UInt16 ) ENGINE = Memory; insert into test.m2 (b) values (1); select * from test.m2; select a, b from test.m2;
表達式列
ALIAS 表達式列某方面跟物化列相同,就是它的值不能從 insert 語句獲取。不同的是, 物化列 是會真正保存數據(這樣查詢時不需要再計算),
而表達式列不會保存數據(這樣查詢時總是需要計算),只是在查詢時返回表達式的結果。
create table test.m3 (a ALIAS (b+1), b UInt16) ENGINE = Memory; insert into test.m3(b) values (1); select * from test.m3; select a, b from test.m3;
引擎是clickhouse設計的精華部分
TinyLog
最簡單的一種引擎,每一列保存為一個文件,里面的內容是壓縮過的,不支持索引
這種引擎沒有并發控制,所以,當你需要在讀,又在寫時,讀會出錯。并發寫,內容都會壞掉。
應用場景:
a. 基本上就是那種只寫一次
b. 然后就是只讀的場景。
c. 不適用于處理量大的數據,官方推薦,使用這種引擎的表最多 100 萬行的數據
drop table if exists test.tinylog; create table test.tinylog (a UInt16, b UInt16) ENGINE = TinyLog; insert into test.tinylog(a,b) values (7,13);
此時/var/lib/clickhouse/data/test/tinylog
保存數據的目錄結構:
├── a.bin ├── b.bin └── sizes.json
a.bin 和 b.bin 是壓縮過的對應的列的數據, sizes.json 中記錄了每個 *.bin 文件的大小
Log
這種引擎跟 TinyLog 基本一致
它的改進點,是加了一個 __marks.mrk 文件,里面記錄了每個數據塊的偏移
這樣做的一個用處,就是可以準確地切分讀的范圍,從而使用并發讀取成為可能
但是,它是不能支持并發寫的,一個寫操作會阻塞其它讀寫操作
Log 不支持索引,同時因為有一個 __marks.mrk 的冗余數據,所以在寫入數據時,一旦出現問題,這個表就廢了
應用場景:
同 TinyLog 差不多,它適用的場景也是那種寫一次之后,后面就是只讀的場景,臨時數據用它保存也可以
drop table if exists test.log; create table test.log (a UInt16, b UInt16) ENGINE = Log; insert into test.log(a,b) values (7,13);
此時/var/lib/clickhouse/data/test/log
保存數據的目錄結構:
├── __marks.mrk ├── a.bin ├── b.bin └── sizes.json
Memory
內存引擎,數據以未壓縮的原始形式直接保存在內存當中,服務器重啟數據就會消失
可以并行讀,讀寫互斥鎖的時間也非常短
不支持索引,簡單查詢下有非常非常高的性能表現
應用場景:
a. 進行測試
b. 在需要非常高的性能,同時數據量又不太大(上限大概 1 億行)的場景
Merge
一個工具引擎,本身不保存數據,只用于把指定庫中的指定多個表鏈在一起。
這樣,讀取操作可以并發執行,同時也可以利用原表的索引,但是,此引擎不支持寫操作
指定引擎的同時,需要指定要鏈接的庫及表,庫名可以使用一個表達式,表名可以使用正則表達式指定
create table test.tinylog1 (id UInt16, name String) ENGINE=TinyLog; create table test.tinylog2 (id UInt16, name String) ENGINE=TinyLog; create table test.tinylog3 (id UInt16, name String) ENGINE=TinyLog; insert into test.tinylog1(id, name) values (1, 'tinylog1'); insert into test.tinylog2(id, name) values (2, 'tinylog2'); insert into test.tinylog3(id, name) values (3, 'tinylog3'); use test; create table test.merge (id UInt16, name String) ENGINE=Merge(currentDatabase(), '^tinylog[0-9]+'); select _table,* from test.merge order by id desc
┌─_table───┬─id─┬─name─────┐
│ tinylog3 │ 3 │ tinylog3 │
│ tinylog2 │ 2 │ tinylog2 │
│ tinylog1 │ 1 │ tinylog1 │
└──────────┴────┴──────────┘
注:
_table 這個列,是因為使用了 Merge 多出來的一個的一個虛擬列
a. 它表示原始數據的來源表,它不會出現在
show table
的結果當中
b.select *
不會包含它
Distributed
與 Merge 類似, Distributed 也是通過一個邏輯表,去訪問各個物理表,設置引擎時的樣子是:
Distributed(remote_group, database, table [, sharding_key])
其中:
remote_group
/etc/clickhouse-server/config.xml中remote_servers參數
database
是各服務器中的庫名
table
是表名
sharding_key
是一個尋址表達式,可以是一個列名,也可以是像 rand() 之類的函數調用,它與 remote_servers 中的 weight 共同作用,決定在 寫 時往哪個 shard 寫
配置文件中的 remote_servers
<remote_servers> <log> <shard> <weight>1</weight> <internal_replication>false</internal_replication> <replica> <host>172.17.0.3</host> <port>9000</port> </replica> </shard> <shard> <weight>2</weight> <internal_replication>false</internal_replication> <replica> <host>172.17.0.4</host> <port>9000</port> </replica> </shard> </log> </remote_servers>
log
是某個 shard 組的名字,就是上面的 remote_group 的值
shard
是固定標簽
weight
是權重,前面說的 sharding_key 與這個有關。
簡單來說,上面的配置,理論上來看:
第一個 shard “被選中”的概率是 1 / (1 + 2) ,第二個是 2 / (1 + 2) ,這很容易理解。但是, sharding_key 的工作情況,是按實際數字的“命中區間”算的,即第一個的區間是 [0, 1) 的周期,第二個區間是 [1, 1+2) 的周期。比如把 sharding_key 設置成 id ,當 id=0 或 id=3 時,一定是寫入到第一個 shard 中,如果把 sharding_key 設置成 rand() ,那系統會對應地自己作一般化轉換吧,這種時候就是一種概率場景了。
internal_replication
是定義針對多個 replica 時的寫入行為的。
如果為 false ,則會往所有的 replica 中寫入數據,但是并不保證數據寫入的一致性,所以這種情況時間一長,各 replica 的數據很可能出現差異。如果為 true ,則只會往第一個可寫的 replica 中寫入數據(剩下的事“物理表”自己處理)。
replica
就是定義各個冗余副本的,選項有 host , port , user , password 等
看一個實際的例子,我們先在兩臺機器上創建好物理表并插入一些測試數據:
create table test.tinylog_d1(id UInt16, name String) ENGINE=TinyLog; insert into test.tinylog_d1(id, name) values (1, 'Distributed record 1'); insert into test.tinylog_d1(id, name) values (2, 'Distributed record 2');
在其中一臺創建邏輯表:
create table test.tinylog_d (id UInt16, name String) ENGINE=Distributed(log, test,tinylog_d1 , id); -- 插入數據到邏輯表,觀察數據分發情況 insert into test.tinylog_d(id, name) values (0, 'main'); insert into test.tinylog_d(id, name) values (1, 'main'); insert into test.tinylog_d(id, name) values (2, 'main'); select name,sum(id),count(id) from test.tinylog_d group by name;
注:
邏輯表中的寫入操作是異步的,會先緩存在本機的文件系統上,并且,對于物理表的不可訪問狀態,并沒有嚴格控制,所以寫入失敗丟數據的情況是可能發生的
Null
空引擎,寫入的任何數據都會被忽略,讀取的結果一定是空。
但是注意,雖然數據本身不會被存儲,但是結構上的和數據格式上的約束還是跟普通表一樣是存在的,同時,你也可以在這個引擎上創建視圖
Buffer
1.
Buffer 引擎,像是Memory 存儲的一個上層應用似的(磁盤上也是沒有相應目錄的)
2.
它的行為是一個緩沖區,寫入的數據先被放在緩沖區,達到一個閾值后,這些數據會自動被寫到指定的另一個表中
3.
和Memory 一樣,有很多的限制,比如沒有索引
4.
Buffer 是接在其它表前面的一層,對它的讀操作,也會自動應用到后面表,但是因為前面說到的限制的原因,一般我們讀數據,就直接從源表讀就好了,緩沖區的這點數據延遲,只要配置得當,影響不大的
5.
Buffer 后面也可以不接任何表,這樣的話,當數據達到閾值,就會被丟棄掉
一些特點:
如果一次寫入的數據太大或太多,超過了 max 條件,則會直接寫入源表。
刪源表或改源表的時候,建議 Buffer 表刪了重建。
“友好重啟”時, Buffer 數據會先落到源表,“暴力重啟”, Buffer 表中的數據會丟失。
即使使用了 Buffer ,多次的小數據寫入,對比一次大數據寫入,也 慢得多 (幾千行與百萬行的差距)
-- 創建源表 create table test.mergetree (sdt Date, id UInt16, name String, point UInt16) ENGINE=MergeTree(sdt, (id, name), 10); -- 創建 Buffer表 -- Buffer(database, table, num_layers, min_time, max_time, min_rows, max_rows, min_bytes, max_bytes) create table test.mergetree_buffer as test.mergetree ENGINE=Buffer(test, mergetree, 16, 3, 20, 2, 10, 1, 10000); insert into test.mergetree (sdt, id, name, point) values ('2017-07-10', 1, 'a', 20); insert into test.mergetree_buffer (sdt, id, name, point) values ('2017-07-10', 1, 'b', 10); select * from test.mergetree; select '------'; select * from test.mergetree_buffer;
database
數據庫
table
源表,這里除了字符串常量,也可以使用變量的。
num_layers
是類似“分區”的概念,每個分區的后面的 min / max 是獨立計算的,官方推薦的值是 16 。
min / max
這組配置薦,就是設置閾值的,分別是 時間(秒),行數,空間(字節)。
閾值的規則:
是“所有的 min 條件都滿足, 或 至少一個 max 條件滿足”。
如果按上面我們的建表來說,所有的 min 條件就是:過了 3秒,2條數據,1 Byte。一個 max 條件是:20秒,或 10 條數據,或有 10K
Set
Set 這個引擎有點特殊,因為它只用在 IN 操作符右側,你不能對它 select
create table test.set(id UInt16, name String) ENGINE=Set; insert into test.set(id, name) values (1, 'hello'); -- select 1 where (1, 'hello') in test.set; -- 默認UInt8 需要手動進行類型轉換 select 1 where (toUInt16(1), 'hello') in test.set;
注:
Set 引擎表,是全內存運行的,但是相關數據會落到磁盤上保存,啟動時會加載到內存中。所以,意外中斷或暴力重啟,是可能產生數據丟失問題的
Join
TODO
MergeTree
這個引擎是 ClickHouse 的重頭戲
,它支持一個日期和一組主鍵的兩層式索引
,還可以實時更新數據
。同時,索引的粒度可以自定義,外加直接支持采樣功能
MergeTree(EventDate, (CounterID, EventDate), 8192) MergeTree(EventDate, intHash42(UserID), (CounterID, EventDate, intHash42(UserID)), 8192)
EventDate
一個日期的列名
intHash42(UserID)
采樣表達式
(CounterID, EventDate)
主鍵組(里面除了列名,也支持表達式),也可以是一個表達式
8192
主鍵索引的粒度
drop table if exists test.mergetree1; create table test.mergetree1 (sdt Date, id UInt16, name String, cnt UInt16) ENGINE=MergeTree(sdt, (id, name), 10); -- 日期的格式,好像必須是 yyyy-mm-dd insert into test.mergetree1(sdt, id, name, cnt) values ('2018-06-01', 1, 'aaa', 10); insert into test.mergetree1(sdt, id, name, cnt) values ('2018-06-02', 4, 'bbb', 10); insert into test.mergetree1(sdt, id, name, cnt) values ('2018-06-03', 5, 'ccc', 11);
此時/var/lib/clickhouse/data/test/mergetree1
的目錄結構:
├── 20180601_20180601_1_1_0 │ ├── checksums.txt │ ├── columns.txt │ ├── id.bin │ ├── id.mrk │ ├── name.bin │ ├── name.mrk │ ├── cnt.bin │ ├── cnt.mrk │ ├── cnt.idx │ ├── primary.idx │ ├── sdt.bin │ └── sdt.mrk -- 保存一下塊偏移量 ├── 20180602_20180602_2_2_0 │ └── ... ├── 20180603_20180603_3_3_0 │ └── ... ├── format_version.txt └── detached
ReplacingMergeTree
1
.在 MergeTree 的基礎上,添加了“處理重復數據”的功能=>實時數據場景
2
.相比 MergeTree ,ReplacingMergeTree 在最后加一個"版本列",它跟時間列配合一起,用以區分哪條數據是"新的",并把舊的丟掉(這個過程是在 merge 時處理,不是數據寫入時就處理了的,平時重復的數據還是保存著的,并且查也是跟平常一樣會查出來的)
3
.主鍵列組用于區分重復的行
-- 版本列 允許的類型是, UInt 一族的整數,或 Date 或 DateTime create table test.replacingmergetree (sdt Date, id UInt16, name String, cnt UInt16) ENGINE=ReplacingMergeTree(sdt, (name), 10, cnt); insert into test.replacingmergetree (sdt, id, name, cnt) values ('2018-06-10', 1, 'a', 20); insert into test.replacingmergetree (sdt, id, name, cnt) values ('2018-06-10', 1, 'a', 30); insert into test.replacingmergetree (sdt, id, name, cnt) values ('2018-06-11', 1, 'a', 20); insert into test.replacingmergetree (sdt, id, name, cnt) values ('2018-06-11', 1, 'a', 30); insert into test.replacingmergetree (sdt, id, name, cnt) values ('2018-06-11', 1, 'a', 10); select * from test.replacingmergetree; -- 如果記錄未執行merge,可以手動觸發一下 merge 行為 optimize table test.replacingmergetree;
┌────────sdt─┬─id─┬─name─┬─cnt─┐
│ 2018-06-11 │ 1 │ a │ 30 │
└────────────┴────┴──────┴─────┘
SummingMergeTree
1
.SummingMergeTree 就是在 merge 階段把數據sum求和
2
.sum求和的列可以指定,不可加的未指定列,會取一個最先出現的值
create table test.summingmergetree (sdt Date, name String, a UInt16, b UInt16) ENGINE=SummingMergeTree(sdt, (sdt, name), 8192, (a)); insert into test.summingmergetree (sdt, name, a, b) values ('2018-06-10', 'a', 1, 20); insert into test.summingmergetree (sdt, name, a, b) values ('2018-06-10', 'b', 2, 11); insert into test.summingmergetree (sdt, name, a, b) values ('2018-06-11', 'b', 3, 18); insert into test.summingmergetree (sdt, name, a, b) values ('2018-06-11', 'b', 3, 82); insert into test.summingmergetree (sdt, name, a, b) values ('2018-06-11', 'a', 3, 11); insert into test.summingmergetree (sdt, name, a, b) values ('2018-06-12', 'c', 1, 35); -- 手動觸發一下 merge 行為 optimize table test.summingmergetree; select * from test.summingmergetree;
┌────────sdt─┬─name─┬─a─┬──b─┐
│ 2018-06-10 │ a │ 1 │ 20 │
│ 2018-06-10 │ b │ 2 │ 11 │
│ 2018-06-11 │ a │ 3 │ 11 │
│ 2018-06-11 │ b │ 6 │ 18 │
│ 2018-06-12 │ c │ 1 │ 35 │
└────────────┴──────┴───┴────┘
注:
可加列不能是主鍵中的列,并且如果某行數據可加列都是 null ,則這行會被刪除
AggregatingMergeTree
AggregatingMergeTree 是在 MergeTree 基礎之上,針對聚合函數結果,作增量計算優化的一個設計,它會在 merge 時,針對主鍵預處理聚合的數據
應用于AggregatingMergeTree 上的聚合函數除了普通的 sum, uniq等,還有 sumState , uniqState ,及 sumMerge , uniqMerge 這兩組
1.
聚合數據的預計算
是一種“空間換時間”的權衡,并且是以減少維度為代價的
dim1 | dim2 | dim3 | measure1 |
---|---|---|---|
aaaa | a | 1 | 1 |
aaaa | b | 2 | 1 |
bbbb | b | 3 | 1 |
cccc | b | 2 | 1 |
cccc | c | 1 | 1 |
dddd | c | 2 | 1 |
dddd | a | 1 | 1 |
假設原始有三個維度,一個需要 count 的指標
dim1 | dim2 | dim3 | measure1 |
---|---|---|---|
aaaa | a | 1 | 1 |
aaaa | b | 2 | 1 |
bbbb | b | 3 | 1 |
cccc | b | 2 | 1 |
cccc | c | 1 | 1 |
dddd | c | 2 | 1 |
dddd | a | 1 | 1 |
通過減少一個維度的方式,來以 count 函數聚合一次 M
dim2 | dim3 | count(measure1) |
---|---|---|
a | 1 | 3 |
b | 2 | 2 |
b | 3 | 1 |
c | 1 | 1 |
c | 2 | 1 |
2.
聚合數據的增量計算
對于 AggregatingMergeTree 引擎的表,不能使用普通的 INSERT 去添加數據,可以用:
a.
INSERT SELECT 來插入數據
b.
更常用的,是可以創建一個物化視圖
drop table if exists test.aggregatingmergetree; create table test.aggregatingmergetree( sdt Date , dim1 String , dim2 String , dim3 String , measure1 UInt64 ) ENGINE=MergeTree(sdt, (sdt, dim1, dim2, dim3), 8192); -- 創建一個物化視圖,使用 AggregatingMergeTree drop table if exists test.aggregatingmergetree_view; create materialized view test.aggregatingmergetree_view ENGINE = AggregatingMergeTree(sdt,(dim2, dim3), 8192) as select sdt,dim2, dim3, uniqState(dim1) as uv from test.aggregatingmergetree group by sdt,dim2, dim3; insert into test.aggregatingmergetree (sdt, dim1, dim2, dim3, measure1) values ('2018-06-10', 'aaaa', 'a', '10', 1); insert into test.aggregatingmergetree (sdt, dim1, dim2, dim3, measure1) values ('2018-06-10', 'aaaa', 'a', '10', 1); insert into test.aggregatingmergetree (sdt, dim1, dim2, dim3, measure1) values ('2018-06-10', 'aaaa', 'b', '20', 1); insert into test.aggregatingmergetree (sdt, dim1, dim2, dim3, measure1) values ('2018-06-10', 'bbbb', 'b', '30', 1); insert into test.aggregatingmergetree (sdt, dim1, dim2, dim3, measure1) values ('2018-06-10', 'cccc', 'b', '20', 1); insert into test.aggregatingmergetree (sdt, dim1, dim2, dim3, measure1) values ('2018-06-10', 'cccc', 'c', '10', 1); insert into test.aggregatingmergetree (sdt, dim1, dim2, dim3, measure1) values ('2018-06-10', 'dddd', 'c', '20', 1); insert into test.aggregatingmergetree (sdt, dim1, dim2, dim3, measure1) values ('2018-06-10', 'dddd', 'a', '10', 1); -- 按 dim2 和 dim3 聚合 count(measure1) select dim2, dim3, count(measure1) from test.aggregatingmergetree group by dim2, dim3; -- 按 dim2 聚合 UV select dim2, uniq(dim1) from test.aggregatingmergetree group by dim2; -- 手動觸發merge OPTIMIZE TABLE test.aggregatingmergetree_view; select * from test.aggregatingmergetree_view; -- 查 dim2 的 uv select dim2, uniqMerge(uv) from test.aggregatingmergetree_view group by dim2 order by dim2;
CollapsingMergeTree
是專門為 OLAP 場景下,一種“變通”存數做法而設計的,在數據是不能改,更不能刪的前提下,通過“運算”的方式,去抹掉舊數據的影響,把舊數據“減”去即可,從而解決"最終狀態"類的問題,比如 當前有多少人在線?
“以加代刪”的增量存儲方式,帶來了聚合計算方便的好處,代價卻是存儲空間的翻倍,并且,對于只關心最新狀態的場景,中間數據都是無用的
CollapsingMergeTree 在創建時與 MergeTree 基本一樣,除了最后多了一個參數,需要指定 Sign 位(必須是 Int8 類型)
create table test.collapsingmergetree(sign Int8, sdt Date, name String, cnt UInt16) ENGINE=CollapsingMergeTree(sdt, (sdt, name), 8192, sign);
感謝各位的閱讀,以上就是“clickhouse基礎知識總結”的內容了,經過本文的學習后,相信大家對clickhouse基礎知識總結這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。