官方文檔鏈接 : https://clickhouse.tech/docs/en/
https://clickhouse.tech/docs/zh/engines/table-engines/mergetree-family/versionedcollapsingmergetree/
1. clickhouse簡介
1.1 概念
ClickHouse是一個用於聯機分析(OLAP)的列式數據庫管理系統(DBMS),是一個完全的列式數據庫管理軟件,支持線性擴展,簡單方便,高可靠性,容錯。
全稱是Click Stream,Data WareHouse。ClickHouse非常適用於商業智能領域,除此之外,它也能夠被廣泛應用於廣告流量、Web、App流量、電信、金融、電子商務、信息安全、網絡游戲、物聯網等眾多其他領域。
相對於傳統的關系型數據庫mysql,Clickhouse 在處理大數據量的時候,在查詢檢索數據量上億條時,其查詢性能有非常強的優勢,在數據量比較小的時候,優勢不明顯。
1.2 優點
-
靈活的MPP架構,支持線性擴展,簡單方便,高可靠
-
多服務器分布式處理數據,完備的DBMS系統
-
底層數據列式存儲,支持壓縮,優化數據存儲,優化索引數據
-
容錯率高,跑分快。比 Vertica 快 5 倍,比Hive 快 279倍,比Mysql 快800 倍,其可處理的數據級別可達到 10 億級別
-
功能多:支持數據統計分析各種場景,支持類SQL查詢,異地復制部署,海量數據存儲,分布式運算,快速閃電的性能,實時數據分析,出色的函數支持
1.3 缺點
-
不支持事務,不支持真正的刪除/更新
-
單節點不支持高並發,一台機器官方建議qps為100 ,但可以通過修改配置文件增加連接數
-
不支持二級索引
-
雖然支持多表join查詢,但不擅長多表join查詢 。 因此在創建表結構的時候,優先考慮創建一張較大較寬的表,將所有的數據放在一張表中
-
元數據管理需要人為干預
-
盡量做到1000條以上的批量數據寫入,避免逐行的insert 或者小批量的insert ,update ,delete 操作,因為寫入一條記錄和1000條記錄所需要的資源和時間相差無幾。
1.4. 應用場景
-
查詢業務遠遠超過增刪改的業務。
-
要求實時返回查詢結果
-
數據需要以大批次(大於1000行)更新,而不是單行更新,或者根本沒有更新操作
-
讀取數據時,會從數據庫中提取出大量的行,但只用到一小部分
-
表很寬,即表中含有大量的字段,列。每次查詢中只會查詢一個大寬表,除了一個大寬表,其余都是小表
-
列的值是比較小的數值和短字符串,而不是較長的文本
-
查詢頻率相對較低,通常每台服務器每秒查詢數百次或更少
-
對於簡單查詢,允許大於50ms的延遲
-
在處理單個查詢時需要提高吞吐量,每台服務器每秒高達數十億行
-
不需要事務
-
數據一致性要求較低( 原子性 持久性 一致性 隔離性 )
-
查詢結果顯著小於數據源,即數據有過濾或者聚合,但查詢返回的結果不超過單個服務器內存的大小
2. 核心概念
2.1 數據分片
數據分片 是將數據進行橫向切分,這是一種在面對海量數據的場景下,解決存儲和查詢瓶頸的有效手段,是一種分治思想的體現,Clickhouse支持分片而分片則依賴集群,每個集群由1到多個分片組成,每個分片對應了Clickhouse的一個服務節點。分片的數量上限取決於節點數量,一個分片只能對應1個服務節點。Clickhouse並不向其他分布式系統那樣,擁有高度自動化的分片功能。Clickhouse的提供了本地表和分布式表的概念,一張本地表等同於一份數據的分片,而分布式表本身並不存儲任何數據,它是本地表的訪問代理,其作用類似分庫中間件。借助分布式表,能夠代理訪問多個數據分片,從而實現分布式查詢,這種設計類似數據庫的分庫和分表,十分靈活。例如在業務系統上線的初期,數據體量並不高,此時數據並不需要多個分片,所以使用單個節點的本地表(單個數據分片)即可滿足業務需求,待到業務增長,數據量增大的時候,再通過新增數據分片的方式分流數據,並通過分布式表實現分布式查詢。
2.2 列式存儲
列式存儲是相對於傳統關系型數據庫的行式存儲來說的。簡單來說兩者的區別就是如何組織表

從上圖可以很清楚地看到,行式存儲下一張表的數據都是放在一起的,但列式存儲下都被分開保存了。將不同的行相同的列存儲再一起,這些數據具有相同的結構和長度,重復率更高,可以壓縮比例更高,降低IO次數,提高查詢效率。
-
在實際的應用場景中,往往需要讀大量行,但是少數幾個列,在行存儲模式下,數據按行連續存儲,所有列的數據都存儲再一個bloCK中,不參與計算的列在IO時也要全部讀出,讀取操作被嚴重放大,在列存儲模式下,值需要讀取參與計算的列即可,極大的降低的IO cast ,加快了查詢效率。
-
更高的壓縮比意味着更小的data size ,從磁盤中讀取相應數據耗時更短。
-
自由的壓縮算法選擇,不同列的數據具有不同的數據類型,適用的壓縮算法也就不盡相同,可以針對不同列類型,選擇最合適的壓縮算法
-
高壓縮比,意味着同樣大小的內存能夠存放更多的數據,系統cache 效果更好
官方顯示,通過適用列式存儲,在某些分析場景中,能后獲得100倍甚至更高的加速效應。
2.3 分區
Clickhouse 支持 PARTITION BY 子句,在建表的時候可以指定按照任意合法表達式進行數據分區操作,比如通過toYYYYMM() 將數據按月進行分區,toMonday() 將數據按照星期進行分區,對Enum 類型的列直接每種取值作為一個分區等,數據以分區的形式統一管理和維護一批數據。
2.4 副本
數據存儲副本,在集群模式下可實現高可用,在CK中通過復制集,實現了數據可靠性,也通過多副本的方式,增加了CK查詢的並發能力。
一般有2種方式:(1)基於ZooKeeper的表復制方式,(2)基於Cluster的復制方式
由於推薦的數據寫入方式為本地表寫入,禁止分布式表寫入,因此復制表主要考慮ZooKeeper的表復制方案。
3. 數據類型
3.1 數字類型
-
IntX和UIntX
-
Int8— [-128 : 127] -
Int16— [-32768 : 32767] -
Int32— [-2147483648 : 2147483647] -
Int64— [-9223372036854775808 : 9223372036854775807] -
Int128— [-170141183460469231731687303715884105728 : 170141183460469231731687303715884105727] -
UInt8— [0 : 255] -
UInt16— [0 : 65535] -
UInt32— [0 : 4294967295] -
UInt64— [0 : 18446744073709551615]
-
FloatX
-
Float32—float. -
Float64—double.
-
Decimal
Decimal32(S) - ( -1 * 10^(9 - S), 1 * 10^(9 - S) )
Decimal64(S) - ( -1 * 10^(18 - S), 1 * 10^(18 - S) )
Decimal128(S) - ( -1 * 10^(38 - S), 1 * 10^(38 - S) )
Decimal256(S) - ( -1 * 10^(76 - S), 1 * 10^(76 - S) )
-
bool
0 或者 1
3.2 字符串類型
-
String
-
FixedString
-
UUID
3.3 時間類型
-
Date
-
DateTime
-
3.4 枚舉
3.5 數組
3.6 Map
CREATE TABLE table_map (a Map(String, UInt64)) ENGINE=Memory;
INSERT INTO table_map VALUES ({'key1':1, 'key2':10}), ({'key1':2,'key2':20}), ({'key1':3,'key2':30});
4. Clickhouse基本操作
4.1 數據庫操作
show databases; // 展示當前有的數據庫
create database if not exists test1 ;//創建數據庫
use test1; // 選擇test1數據庫
select currentDatabase() ;查看當前使用的數據庫
drop database test1; // 刪除數據庫
創建數據庫語法:
CREATE database if not exists “數據庫名稱”;
CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster] [ENGINE = engine(...)]
示例:CREATE database if not exists test_for_szp ;
默認情況下,ClickHouse使用的是原生的數據庫引擎Ordinary(在此數據庫下可以使用任意類型的表引擎,在絕大多數情況下都只需使用默認的數據庫引擎)。當然也可以使用Lazy引擎和MySQL引擎,比如使用MySQL引擎,可以直接在ClickHouse中操作MySQL對應數據庫中的表。假設MySQL中存在一個名為clickhouse的數據庫,可以使用下面的方式連接MySQL數據庫。
-- --------------------------語法-----------------------------------
CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster]
ENGINE = MySQL('host:port', ['database' | database], 'user', 'password')
-- --------------------------示例------------------------------------
CREATE DATABASE mysql_db ENGINE = MySQL('192.168.200.241:3306', 'clickhouse', 'root', '123qwe');
刪除數據庫
語法:DROP database “數據庫名稱”;
示例:DROP database test_for_szp;
4.2 數據表操作
ck創建數據表的時候,一定要指定引擎,否則會報錯
create table tb_test1(
id int,
name String
)engine=Memory;
CREATE TABLE product_id
(
factory_goods_id UInt32 COMMENT '工廠商品ID',
goods_name String COMMENT '商品名稱',
shop_id UInt32 COMMENT '店鋪ID',
shop_name String COMMENT '店鋪名稱',
create_time DateTime COMMENT '創建時間',
update_time DateTime COMMENT '更新時間'
) ENGINE = MergeTree()
PRIMARY KEY factory_goods_id
ORDER BY factory_goods_id
CREATE TABLE wenl1.wenl_tmp_fact_general_report_windspeed (
data_time Date,
avg_of_wind_speed Float32
) ENGINE = MergeTree()
ORDER BY data_time;
刪除表
DROP table if exists product_detail;
DROP table if exists product_id;
在集群上部署的Clickhouse ,刪除表也應該帶集群的名稱
DROP TABLE wenl1.wenl_tmp_fact_general_report_substation on CLUSTER elune;
刪除數據表的時候會遇到一個問題:
// 刪除數據表
DROP TABLE if exists wenl1.wenl_tmp_fact_general_report_substation on CLUSTER elune;
// 重建數據表
CREATE TABLE if not exists wenl1.wenl_tmp_fact_general_report_production on cluster elune(
date_time DATE,
active_power_sum FLOAT(32),
wtg_id String,
site_id String
)
ENGINE =ReplicatedReplacingMergeTree('/clickhouse/wenl1/tables/{layer}-{shard}/wenl_tmp_fact_general_report_production', '{replica}')
order by (date_time)
settings index_granularity = 8192;
會出現以下報錯:
SQL 錯誤 [253]: ClickHouse exception, code: 253, host: 10.65.19.56, port: 8123; Code: 253, e.displayText() = DB::Exception: There was an error on [clickhouse0006.eniot.io:9000]: Code: 253, e.displayText() = DB::Exception: Replica /clickhouse/wenl1/tables/01-01/wenl_tmp_fact_general_report_production/replicas/elune-01-2 already exists. (version 21.4.3.21-edh-3.1.4) (version 21.4.3.21-edh-3.1.4)
在我們刪除本地表和分布式表后,立即重建是沒有問題的。唯一有問題的就是復制表,因為復制表需要在zookeeper上建立一個路徑,存放相關數據。clickhouse默認的庫引擎是原子數據庫引擎,刪除Atomic數據庫中的表后,它不會立即刪除,而是會在480秒后刪除。由下面這個參數控制: 
我們可以使用以下辦法來解決這個問題:
-
使用普通數據庫而不是原子數據庫。 create database … Engine=Ordinary.
-
使用uniq ZK路徑。{uuid}/clickhouse/tables/{layer}-{shard}-{uuid}/
-
減少database_atomic_delay_before_drop_table_sec = 0 & drop table … sync
4.3 插入數據
INSERT INTO [db.]table [(c1, c2, c3)] VALUES (v11, v12, v13), (v21, v22, v23), ...
INSERT into wenl_wtg_production_day
( wtg_id, date_time ,positive_energy_p1 ,positive_energy_point ,positive_energy)
values
('wtg_id1', '2021-01-01 00:00:00' ,1.0 ,1.0 ,1.0 );
INSERT into wenl_wtg_production_day
( wtg_id, date_time ,positive_energy_p1 ,positive_energy_point ,positive_energy)
values
('04m1sTS5', '2021-01-06' ,1.0 ,1.0 ,1.0 ),
('04s4109D', '2021-01-06' ,1.0 ,1.0 ,1.0 ),
('0C04iY2k', '2021-01-06' ,1.0 ,1.0 ,1.0 ),
('0Mx32xyS', '2021-01-06' ,1.0 ,1.0 ,1.0 ),
('0QRgFwFQ', '2021-01-06' ,1.0 ,1.0 ,1.0 ),
('0ZbfFyQR', '2021-01-06' ,1.0 ,1.0 ,1.0 ),
('0bVTxmOo', '2021-01-06' ,1.0 ,1.0 ,1.0 ),
('0rP1oCJY', '2021-01-06' ,1.0 ,1.0 ,1.0 );
INSERT into wenl_dm_fact_daily_report_10m_detail_tmp
(org_id, site_id, wtg_id,data_time, update_time,wind_speed,read_wind_speed,tem_out, active_power,theory_power,production_loss )
values
('org_id4', 'site_id4' ,'wtg_id4', '2021-01-04:00:00:00', '2021-01-04:00:00:00',40.0, 40.0, 40.0, 40.0, 40.0, 40.0 