ClickHouse介紹
ClickHouse的由來和應用場景
- 俄羅斯Yandex在2016年開源,使用C++編寫的列式存儲數據庫,近幾年在OLAP領域大範圍應用
- 官網:https://clickhouse.com/
- GitHub: https://github.com/ClickHouse/ClickHouse
docker安裝
docker run -d --name ybchen_clickhouse --ulimit nofile=262144:262144 \
-p 8123:8123 -p 9000:9000 -p 9009:9009 --privileged=true \
-v /mydata/docker/clickhouse/log:/var/log/clickhouse-server \
-v /mydata/docker/clickhouse/data:/var/lib/clickhouse clickhouse/clickhouse-server:22.2.3.5
-
默認http端口是8123,tcp端口是9000, 同步端口9009
-
進入容器內部查看
-
web可視化界面:http://ip:8123/play
-
命令
- 查看數據庫 SHOW DATABASES
- 查看某個庫下面的全部表 SHOW TABLES IN system
- 系統數據庫是 ClickHouse 存儲有關 ClickHouse 部署的詳細信息的地方
-
默認數據庫最初爲空,用於執行未指定數據庫的命令
-
可視化工具
創建庫
CREATE DATABASE shop
創建表
CREATE TABLE shop.clickstream ( customer_id String, time_stamp Date, click_event_type String, page_code FixedString(20), source_id UInt64 ) ENGINE = MergeTree() ORDER BY (time_stamp)
- ClickHouse 有自己的數據類型,每個表都必須指定一個Engine引擎屬性來確定要創建的表的類型
- 引擎決定了數據的存儲方式和存儲位置、支持哪些查詢、對併發的支持
插入數據
INSERT INTO shop.clickstream VALUES ('customer1', '2021-10-02', 'add_to_cart', 'home_enter', 568239 )
查詢數據
SELECT * FROM shop.clickstream
SELECT * FROM shop.clickstream WHERE time_stamp >= '2001-11-01'
數據類型
數值類型(整形,浮點數,定點數)
整型
- 固定長度的整型,包括有符號整型或無符號整型 IntX X是位的意思,1Byte字節=8bit位
有符號整型範圍
Int8 — [-128 : 127]
Int16 — [-32768 : 32767]
Int32 — [-2147483648 : 2147483647]
Int64 — [-9223372036854775808 : 9223372036854775807]
Int128 — [-170141183460469231731687303715884105728 : 170141183460469231731687303715884105727]
Int256 — [-57896044618658097711785492504343953926634992332820282019728792003956564819968 : 57896044618658097711785492504343953926634992332820282019728792003956564819967]
無符號整型範圍
UInt8 — [0 : 255]
UInt16 — [0 : 65535]
UInt32 — [0 : 4294967295]
UInt64 — [0 : 18446744073709551615]
UInt128 — [0 : 340282366920938463463374607431768211455]
UInt256 — [0 : 115792089237316195423570985008687907853269984665640564039457584007913129639935]
浮點型(存在精度損失問題)
- 建議儘可能以整型形式存儲數據
- Float32 - mysql裏面的float類型
- Float64 - mysql裏面的double類型
Decimal類型
-
需要要求更高的精度的數值運算,則需要使用定點數
-
一般金額字段、匯率、利率等字段爲了保證小數點精度,都使用 Decimal
-
Clickhouse提供了Decimal32,Decimal64,Decimal128三種精度的定點數
-
用Decimal(P,S)來定義:
- P代表精度(Precise),表示總位數(整數部分 + 小數部分)
- S代表規模(Scale),表示小數位數
-
例子:Decimal(10,2) 小數部分2位,整數部分 8位(10-2)
-
也可以使用Decimal32(S)、Decimal64(S)和Decimal128(S)的方式來表示
-
CREATE TABLE shop.clickstream1 (
customer_id String,
time_stamp Date,
click_event_type String,
page_code FixedString(20),
source_id UInt64,
money Decimal(2,1)
)
ENGINE = MergeTree()
ORDER BY (time_stamp)
INSERT INTO shop.clickstream1
VALUES ('customer1', '2021-10-02', 'add_to_cart', 'home_enter', 568239,2.11 )
字符串類型
UUID
- 通用唯一標識符(UUID)是由一組32位數的16進制數字所構成,用於標識記錄
61f0c404-5cb3-11e7-907b-a6006ad3dba0
-
要生成UUID值,ClickHouse提供了 generateuidv4 函數。
-
如果在插入新記錄時未指定UUID列的值,則UUID值將用零填充
00000000-0000-0000-0000-000000000000
- 建表和插入例子
CREATE TABLE t_uuid (x UUID, y String) ENGINE=TinyLog
INSERT INTO t_uuid SELECT generateUUIDv4(), 'Example 1'
FixedString固定字符串類型(相對少用)
-
類似MySQL的Char類型,屬於定長字符,固定長度 N 的字符串(N 必須是嚴格的正自然數)
-
如果字符串包含的字節數少於`N’,將對字符串末尾進行空字節填充。
-
如果字符串包含的字節數大於
N
,將拋出Too large value for FixedString(N)
異常。 -
當數據的長度恰好爲N個字節時,
FixedString
類型是高效的,在其他情況下,這可能會降低效率-
應用場景
- ip地址二進制表示的IP地址
- 語言代碼(ru_RU, en_US … )
- 貨幣代碼(USD, RUB …
-
String 字符串類型
- 字符串可以任意長度的。它可以包含任意的字節集,包含空字節
- 字符串類型可以代替其他 DBMSs中的 VARCHAR、BLOB、CLOB 等類型
- ClickHouse 沒有編碼的概念,字符串可以是任意的字節集,按它們原本的方式進行存儲和輸出
時間類型
-
Date
- 日期類型,用兩個字節存儲,表示從 1970-01-01 (無符號) 到當前的日期值,支持字符串形式寫入
- 上限是2106年,但最終完全支持的年份爲2105
-
DateTime
- 時間戳類型。用四個字節(無符號的)存儲 Unix 時間戳,支持字符串形式寫入
- 時間戳類型值精確到秒
- 值的範圍: [1970-01-01 00:00:00, 2106-02-07 06:28:15]
-
DateTime64
- 此類型允許以日期(date)加時間(time)的形式來存儲一個時刻的時間值,具有定義的亞秒精度
- 值的範圍: [1925-01-01 00:00:00, 2283-11-11 23:59:59.99999999] (注意: 最大值的精度是8)
枚舉類型
-
包括
Enum8
和Enum16
類型,Enum
保存'string'= integer
的對應關係 -
在 ClickHouse 中,儘管用戶使用的是字符串常量,但所有含有
Enum
數據類型的操作都是按照包含整數的值來執行。這在性能方面比使用String
數據類型更有效。Enum8
用'String'= Int8
對描述。Enum16
用'String'= Int16
對描述。
-
創建一個帶有一個枚舉
Enum8('home' = 1, 'detail' = 2, 'pay'=3)
類型的列
CREATE TABLE t_enum
(
page_code Enum8('home' = 1, 'detail' = 2,'pay'=3)
)
ENGINE = TinyLog
- 插入, page_code 這列只能存儲類型定義中列出的值:
'home'
或`'detail' 或 'pay'。如果您嘗試保存任何其他值,ClickHouse 拋出異常
#插入成功
INSERT INTO t_enum VALUES ('home'), ('detail')
#插入報錯
INSERT INTO t_enum VALUES ('home1')
#查詢
SELECT * FROM t_enum
布爾值
- 舊版以前沒有單獨的類型來存儲布爾值。可以使用 UInt8 類型,取值限制爲 0 或 1
- 新增裏面新增了Bool
CREATE TABLE shop.clickstream2 (
customer_id String,
time_stamp Date,
click_event_type String,
page_code FixedString(20),
source_id UInt64,
money Decimal(2,1),
is_new Bool
)
ENGINE = MergeTree()
ORDER BY (time_stamp)
DESCRIBE shop.clickstream2
INSERT INTO shop.clickstream2
VALUES ('customer1', '2021-10-02', 'add_to_cart', 'home_enter', 568239, 3.8,1)
查看當前版本支持的數據類型
select * from system.data_type_families
- case_insensitive 選項爲1 表示大小寫不敏感,字段類型不區分大小寫
- 爲0 表示大小寫敏感,即字段類型需要嚴格區分大小寫
- 裏面很多數據類型,記住常用的即可
Mysql數據類型對比
ClickHouse | Mysql | 說明 |
---|---|---|
UInt8 | UNSIGNED TINYINT | |
Int8 | TINYINT | |
UInt16 | UNSIGNED SMALLINT | |
Int16 | SMALLINT | |
UInt32 | UNSIGNED INT, UNSIGNED MEDIUMINT | |
Int32 | INT, MEDIUMINT | |
UInt64 | UNSIGNED BIGINT | |
Int64 | BIGINT | |
Float32 | FLOAT | |
Float64 | DOUBLE | |
Date | DATE | |
DateTime | DATETIME, TIMESTAMP | |
FixedString | BINARY |
常見SQL語法和注意事項
官方文檔
-
ClickHouse語法和常規SQL語法類似,多數都是支持的
創建表
CREATE TABLE shop.clickstream3 (
customer_id String,
time_stamp Date,
click_event_type String,
page_code FixedString(20),
source_id UInt64,
money Decimal(2,1),
is_new Bool
)
ENGINE = MergeTree()
ORDER BY (time_stamp)
查看錶結構
DESCRIBE shop.clickstream3
查詢
SELECT * FROM shop.clickstream3
插入
INSERT INTO shop.clickstream3
VALUES ('customer2', '2021-10-02', 'add_to_cart', 'home_enter', 568239,2.1, False )
更新和刪除
-
在OLAP數據庫中,可變數據(Mutable data)通常是不被歡迎的,早期ClickHouse是不支持,後來版本纔有
-
不支持事務,建議批量操作,不要高頻率小數據量更新刪除
-
刪除和更新是一個異步操作的過程,語句提交立刻返回,但不一定已經完成了
- 判斷是否完成
SELECT database, table, command, create_time, is_done FROM system.mutations LIMIT 20
更新
ALTER TABLE shop.clickstream3 UPDATE click_event_type = 'pay' where customer_id = 'customer2';
刪除
ALTER TABLE shop.clickstream3 delete where customer_id = 'customer2';
分片-分區-副本
什麼是ClickHouse的分區
-
分區是表的分區,把一張表的數據分成N多個區塊,分區後的表還是一張表,數據處理還是由自己來完成
-
PARTITION BY,指的是一個表按照某一列數據(比如日期)進行分區,不同分區的數據會寫入不同的文件中
-
建表時加入partition概念,可以按照對應的分區字段,允許查詢在指定了分區鍵的條件下,儘可能的少讀取數據
create table shop.order_merge_tree(
id UInt32,
sku_id String,
out_trade_no String,
total_amount Decimal(16,2),
create_time Datetime
) engine =MergeTree()
partition by toYYYYMMDD(create_time)
order by (id,sku_id)
primary key (id);
注意
- 不是所有的表引擎都可以分區,合併樹(MergeTree) 系列的表引擎才支持數據分區,Log系列引擎不支持
什麼是ClickHouse的分片
- Shard 分片是把數據庫橫向擴展(Scale Out)到多個物理節點上的一種有效的方式
- 複用了數據庫的分區概念,相當於在原有的分區下作爲第二層分區,ClickHouse會將數據分爲多個分片,並且分佈到不同節點上,再通過 Distributed 表引擎把數據拼接起來一同使用
- Sharding機制使得ClickHouse可以橫向線性拓展,構建大規模分佈式集羣,但需要避免數據傾斜問題
什麼是ClickHouse的副本
- 兩個相同數據的表, 作用是爲了數據備份與安全,保障數據的高可用性,
- 即使一臺 ClickHouse 節點宕機,那麼也可以從其他服務器獲得相同的數據
- 類似Mysql主從架構,主節點宕機,從節點也能提供服務
總結
- 數據分區-允許查詢在指定了分區鍵的條件下,儘可能的少讀取數據
- 數據分片-允許多臺機器/節點同並行執行查詢,實現了分佈式並行計算
ClickHouse常見引擎
Log系列
-
最小功能的輕量級引擎,當需要快速寫入許多小表並在以後整體讀取它們時效果最佳,一次寫入多次查詢
-
種類
- TinyLog、StripLog、Log
MergeTree系列
-
CLickhouse最強大的表引擎,有多個不同的種類
-
適用於高負載任務的最通用和功能最強大的表引擎,可以快速插入數據並進行後續的後臺數據處理
-
支持主鍵索引、數據分區、數據副本等功能特性和一些其他引擎不支持的其他功能
-
種類
- MergeTree、ReplacingMergeTree
- SummingMergeTree、AggregatingMergeTree
- CollapsingMergeTree、VersionedCollapsingMergeTree、GraphiteMergeTree
外部存儲引擎系列
-
能夠直接從其它的存儲系統讀取數據,例如直接讀取 HDFS 的文件或者 MySQL 數據庫的表,這些表引擎只負責元數據管理和數據查詢
-
種類
- HDFS、Mysql
- Kafka、JDBC
其他特定引擎
-
Memory
- 原生數據直接存儲內存,性能高,重啓則消失,讀寫不會阻塞,不支持索引,主要是測試使用
-
Distributed
- 分佈式引擎本身不存儲數據, 但可以在多個服務器上進行分佈式查詢。 讀是自動並行的。讀取時,遠程服務器表的索引(如果有的話)會被使用
-
File
- 數據源是以 Clickhouse 支持的一種輸入格式(TabSeparated,Native等)存儲數據的文件
- 從 ClickHouse 導出數據到文件,將數據從一種格式轉換爲另一種格式
-
Merge
- Merge 引擎 (不要跟 MergeTree 引擎混淆) 本身不存儲數據,但可用於同時從任意多個其他的表中讀取數據。讀是自動並行的,不支持寫入,讀取時,那些被真正讀取到數據的表的索引(如果有的話)會被使用
-
Set、Buffer、Dictionary等
ClickHouse的MergeTree表引擎
特點
-
ClickHouse 不要求主鍵唯一,所以可以插入多條具有相同主鍵的行。
-
如果指定了【分區鍵】則可以使用【分區】,可以通過PARTITION 語句指定分區字段,合理使用數據分區,可以有效減少查詢時數據文件的掃描範圍
-
在相同數據集的情況下 ClickHouse 中某些帶分區的操作會比普通操作更快,查詢中指定了分區鍵時 ClickHouse 會自動截取分區數據,這也有效增加了查詢性能
-
支持數據副本和數據採樣
- 副本是表級別的不是整個服務器級的,所以服務器裏可以同時有複製表和非複製表。
- 副本不依賴分片,每個分片有它自己的獨立副本
語法
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2],
...
INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,
INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
) ENGINE = MergeTree()
ORDER BY expr
[PARTITION BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...]
[SETTINGS name=value, ...]
語法解析
- 【必填】
ENGINE
- 引擎名和參數。ENGINE = MergeTree()
.MergeTree
引擎沒有參數
-
【必填】
ORDER BY
— 排序鍵,可以是一組列的元組或任意的表達式- 例如:
ORDER BY (CounterID, EventDate)
。如果沒有使用PRIMARY KEY
顯式指定的主鍵,ClickHouse 會使用排序鍵作爲主鍵 - 如果不需要排序,可以使用
ORDER BY tuple()
- 例如:
-
【選填】
PARTITION BY
— 分區鍵 ,可選項- 要按月分區,可以使用表達式
toYYYYMM(date_column)
,這裏的date_column
是一個 Date 類型的列, 分區名的格式會是"YYYYMM"
- 分區的好處是降低掃描範圍提升速度,不填寫默認就使用一個分區
- 要按月分區,可以使用表達式
-
【選填】
PRIMARY KEY
-主鍵,作爲數據的一級索引,但是不是唯一約束,和其他數據庫區分- 如果要 選擇與排序鍵不同的主鍵,在這裏指定,可選項,
- 默認情況下主鍵跟排序鍵(由
ORDER BY
子句指定)相同。 - 大部分情況下不需要再專門指定一個
PRIMARY KEY
-
PRIMARY KEY 主鍵必須是 order by 字段的前綴字段。
- 主鍵和排序字段這兩個屬性只設置一個時,另一個默認與它相同, 當兩個都設置時,PRIMARY KEY必須爲ORDER BY的前綴
- 比如ORDER BY (CounterID, EventDate),那主鍵需要是(CounterID )或 (CounterID, EventDate)
合併樹MergeTree實戰
建表
create table shop.order_merge_tree(
id UInt32,
sku_id String,
out_trade_no String,
total_amount Decimal(16,2),
create_time Datetime
) engine =MergeTree()
order by (id,sku_id)
partition by toYYYYMMDD(create_time)
primary key (id);
insert into shop.order_merge_tree values
(1,'sku_1','aabbcc',5600.00,'2023-03-01 16:00:00') ,
(2,'sku_2','23241',4.02,'2023-03-01 17:00:00'),
(3,'sku_3','542323',55.02,'2023-03-01 18:00:00'),
(4,'sku_1','54222',2000.3,'2023-04-01 19:00:00'),
(5,'sku_2','53423',120.2,'2023-04-01 19:00:00'),
(6,'sku_4','65432',600.01,'2023-04-02 11:00:00');
進入容器
docker exec -it 35dad2d981d5 /bin/bash
分區合併驗證
新的數據寫入會有臨時分區產生,不之間加入已有分區
寫入完成後經過一定時間(10到15分鐘),ClickHouse會自動化執行合併操作,將臨時分區的數據合併到已有分區當中
optimize的合併操作是在後臺執行的,無法預測具體執行時間點,除非是手動執行
通過手工合併( optimize table xxx final; )
在數據量比較大的情況,儘量不要使用該命令,執行optimize要消耗大量時間
create table shop.order_merge_tree(
id UInt32,
sku_id String,
out_trade_no String,
total_amount Decimal(16,2),
create_time Datetime
) engine =MergeTree()
order by (id,sku_id)
partition by toYYYYMMDD(create_time)
primary key (id);
insert into shop.order_merge_tree values
(1,'sku_1','aabbcc',5600.00,'2023-03-01 16:00:00') ,
(2,'sku_2','23241',4.02,'2023-03-01 17:00:00'),
(3,'sku_3','542323',55.02,'2023-03-01 18:00:00'),
(4,'sku_1','54222',2000.3,'2023-04-01 19:00:00'),
(5,'sku_2','53423',120.2,'2023-04-01 19:00:00'),
(6,'sku_4','65432',600.01,'2023-04-02 11:00:00');
docker exec -it 35dad2d981d5 /bin/bash
clickhouse-client
optimize table shop.order_merge_tree final;
不使用分區演示
create table shop.order_merge_tree22(
id UInt32,
sku_id String,
out_trade_no String,
total_amount Decimal(16,2),
create_time Datetime
) engine =MergeTree()
order by (id,sku_id)
primary key (id);
insert into shop.order_merge_tree22 values
(1,'sku_1','aabbcc',5600.00,'2023-03-01 16:00:00') ,
(2,'sku_2','23241',4.02,'2023-03-01 17:00:00'),
(3,'sku_3','542323',55.02,'2023-03-01 18:00:00'),
(4,'sku_1','54222',2000.3,'2023-04-01 19:00:00'),
(5,'sku_2','53423',120.2,'2023-04-01 19:00:00'),
(6,'sku_4','65432',600.01,'2023-04-02 11:00:00');
總結
- 使用過分區鍵,會通過分區鍵,將數據落到不同分區中,提升查詢效率
- 沒使用過分區鍵,數據全部落一個分區中
合併樹ReplacingMergeTree實戰
介紹
-
MergeTree的拓展,該引擎和 MergeTree 的不同之處在它會刪除【排序鍵值】相同重複項,根據OrderBy字段
-
數據的去重只會在數據合併期間進行。合併會在後臺一個不確定的時間進行,因此你無法預先作出計劃。
-
有一些數據可能仍未被處理,儘管可以調用 OPTIMIZE 語句發起計劃外的合併,但請不要依靠它,因爲 OPTIMIZE 語句會引發對數據的大量讀寫
-
因此,ReplacingMergeTree 適用於在後臺清除重複的數據以節省空間,但是它不保證沒有重複的數據出現
-
注意去重訪問
-
如果是有多個分區表,只在分區內部進行去重,不會跨分區
-
語法
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = ReplacingMergeTree([ver])
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
參數
-
ver
— 版本列。類型爲UInt*
,Date
或DateTime
。可選參數。在數據合併的時候,
ReplacingMergeTree
從所有具有相同排序鍵的行中選擇一行留下:- 如果
ver
列未指定,保留最後一條。 - 如果
ver
列已指定,保留ver
值最大的版本
- 如果
-
如何判斷數據重複
- 在去除重複數據時,是以ORDER BY排序鍵爲基準的,而不是PRIMARY KEY
- 若排序字段爲兩個,則兩個字段都相同時纔會去重
-
何時刪除重複數據
- 在執行分區合併時觸發刪除重複數據,optimize的合併操作是在後臺執行的,無法預測具體執行時間點,除非是手動執行
-
不同分區的重複數據不會被去重
- ReplacingMergeTree是以分區爲單位刪除重複數據的,在相同的數據分區內重複的數據纔會被刪除,而不同數據分區之間的重複數據依然不能被刪除的
-
刪除策略
- ReplacingMergeTree() 填入的參數爲版本字段,重複數據就會保留版本字段值最大的。
- 如果不填寫版本字段,默認保留插入順序的最後一條數據
建表
-
ver表示的列只能是UInt*,Date和DateTime 類型
-
刪除策略
- ReplacingMergeTree() 填入的參數爲版本字段,重複數據就會保留版本字段值最大的。
- 如果不填寫版本字段,默認保留插入順序的最後一條數據
create table shop.order_relace_merge_tree(
id UInt32,
sku_id String,
out_trade_no String,
total_amount Decimal(16,2),
create_time Datetime
) engine =ReplacingMergeTree(id)
order by (sku_id)
partition by toYYYYMMDD(create_time)
primary key (sku_id);
insert into shop.order_relace_merge_tree values
(1,'sku_1','aabbcc',5600.00,'2023-03-01 16:00:00') ,
(2,'sku_2','23241',4.02,'2023-03-01 17:00:00'),
(3,'sku_3','542323',55.02,'2023-03-01 18:00:00'),
(4,'sku_5','54222',2000.3,'2023-04-01 19:00:00'),
(5,'sku_6','53423',120.2,'2023-04-01 19:00:00'),
(6,'sku_7','65432',600.01,'2023-04-02 11:00:00');
insert into shop.order_relace_merge_tree values
(11,'sku_1','aabbcc',5600.00,'2023-03-01 16:00:00') ,
(21,'sku_2','23241',4.02,'2023-03-01 17:00:00'),
(31,'sku_3','542323',55.02,'2023-03-01 18:00:00'),
(41,'sku_5','54222',2000.3,'2023-04-01 19:00:00'),
(51,'sku_8','53423',120.2,'2023-04-01 19:00:00'),
(61,'sku_9','65432',600.01,'2023-04-02 11:00:00');
docker exec -it 35dad2d981d5 /bin/bash
clickhouse-client
SELECT * FROM shop.order_relace_merge_tree
optimize table shop.order_relace_merge_tree final;
演示
create table shop.order_relace_merge_tree(
id UInt32,
sku_id String,
out_trade_no String,
total_amount Decimal(16,2),
create_time Datetime
) engine =ReplacingMergeTree(id)
order by (sku_id)
partition by toYYYYMMDD(create_time)
primary key (sku_id);
insert into shop.order_relace_merge_tree values
(1,'sku_1','aabbcc',5600.00,'2023-03-01 16:00:00') ,
(2,'sku_2','23241',4.02,'2023-03-01 17:00:00'),
(3,'sku_3','542323',55.02,'2023-03-01 18:00:00'),
(4,'sku_5','54222',2000.3,'2023-04-01 19:00:00'),
(5,'sku_6','53423',120.2,'2023-04-01 19:00:00'),
(6,'sku_7','65432',600.01,'2023-04-02 11:00:00');
insert into shop.order_relace_merge_tree values
(11,'sku_1','aabbcc',5600.00,'2023-03-01 16:00:00') ,
(21,'sku_2','23241',4.02,'2023-03-01 17:00:00'),
(31,'sku_3','542323',55.02,'2023-03-01 18:00:00'),
(41,'sku_5','54222',2000.3,'2023-04-01 19:00:00'),
(51,'sku_8','53423',120.2,'2023-04-01 19:00:00'),
(61,'sku_9','65432',600.01,'2023-04-02 11:00:00');
docker exec -it 35dad2d981d5 /bin/bash
clickhouse-client
SELECT * FROM shop.order_relace_merge_tree
optimize table shop.order_relace_merge_tree final;
合併樹SummingMergeTree實戰
介紹
-
該引擎繼承自 MergeTree,區別在於,當合並 SummingMergeTree 表的數據片段時,ClickHouse 會把所有具有 相同OrderBy排序鍵 的行合併爲一行,該行包含了被合併的行中具有數值類型的列的彙總值。
-
類似group by的效果,這個可以顯著的減少存儲空間並加快數據查詢的速度
-
推薦將該引擎和 MergeTree 一起使用。例如在準備做數據報表的時候,將完整的數據存儲在 MergeTree 表中,並且使用 SummingMergeTree 來存儲聚合數據,可以使避免因爲使用不正確的 排序健組合方式而丟失有價值的數據
-
只需要查詢彙總結果,不關心明細數據
-
設計聚合統計表,字段全部是維度、度量或者時間戳即可,非相關的字段可以不添加
-
獲取彙總值,不能直接 select 對應的字段,而需要使用 sum 進行聚合,因爲自動合併的部分可能沒進行,會導致一些還沒來得及聚合的臨時明細數據少
語法
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = SummingMergeTree([columns])
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
參數
- columns 包含了將要被彙總的列的列名的元組。可選參數。
- 所選的【列必須是數值類型】,具有 相同OrderBy排序鍵 的行合併爲一行
- 如果沒有指定
columns
,ClickHouse 會把非維度列且是【數值類型的列】都進行彙總
建表
create table shop.order_summing_merge_tree(
id UInt32,
sku_id String,
out_trade_no String,
total_amount Decimal(16,2),
create_time Datetime
) engine =SummingMergeTree(total_amount)
order by (id,sku_id)
partition by toYYYYMMDD(create_time)
primary key (id);
insert into shop.order_summing_merge_tree values
(1,'sku_1','aabbcc',5600.00,'2023-03-01 16:00:00') ,
(2,'sku_2','23241',4.02,'2023-03-01 17:00:00'),
(3,'sku_3','542323',55.02,'2023-03-01 18:00:00'),
(4,'sku_5','54222',2000.3,'2023-04-01 19:00:00'),
(5,'sku_6','53423',120.2,'2023-04-01 19:00:00'),
(6,'sku_7','65432',600.01,'2023-04-02 11:00:00');
insert into shop.order_summing_merge_tree values
(1,'sku_1','aabbccbb',5600.00,'2023-03-01 23:09:00')
select sku_id,sum(total_amount) from shop.order_summing_merge_tree group by sku_id
optimize table shop.order_summing_merge_tree final;
演示
總結
-
SummingMergeTree是根據什麼對數據進行合併的
- 【ORBER BY排序鍵相同】作爲聚合數據的條件Key的行中的列進行彙總,將這些行替換爲包含彙總數據的一行記錄
-
** 跨分區內的相同排序key的數據是否會進行合併**
- 以數據分區爲單位來聚合數據,同一數據分區內相同ORBER BY排序鍵的數據會被合併彙總,而不同分區之間的數據不會被彙總
-
如果沒有指定聚合字段,會怎麼聚合
- 如果沒有指定聚合字段,則會用非維度列,且是數值類型字段進行聚合
-
對於非彙總字段的數據,該保留哪一條
- 如果兩行數據除了【ORBER BY排序鍵】相同,其他的非聚合字段不相同,在聚合時會【保留最初】的那條數據,新插入的數據對應的那個字段值會被捨棄
-
在合併分區的時候按照預先定義的條件聚合彙總數據,將同一分區下的【相同排序】的多行數據彙總合併成一行,既減少了數據行節省空間,又降低了後續彙總查詢的開銷
Clickhouse高可用集羣搭建
部署zookeeper
- 副本同步需要藉助zookeeper實現數據的同步, 副本節點會在zk上進行監聽,但數據同步過程是不經過zk的
- zookeeper要求3.4.5以及以上版本
docker run -d --name ybchen_zookeeper -p 2181:2181 -t zookeeper:3.7.0
部署Clickhouse
機器 | 公網 | 私網 | hostname |
機器-1 | 47.116.143.16 | 172.16.0.103 | ybchen-1 |
機器-2 | 101.132.27.2 | 172.16.0.108 | ybchen-2 |
Linux機器安裝ClickHouse,版本:ClickHouse 22.1.2.2-2
文檔地址:https://clickhouse.com/docs/zh/getting-started/install
#各個節點上傳到新建文件夾 /usr/local/software/* #安裝 sudo rpm -ivh *.rpm #啓動 systemctl start clickhouse-server #停止 systemctl stop clickhouse-server #重啓 systemctl restart clickhouse-server #狀態查看 sudo systemctl status clickhouse-server #查看端口占用,如果命令不存在 yum install -y lsof lsof -i :8123 #查看日誌 tail -f /var/log/clickhouse-server/clickhouse-server.log tail -f /var/log/clickhouse-server/clickhouse-server.err.log #開啓遠程訪問,取消下面的註釋 vim /etc/clickhouse-server/config.xml #編輯配置文件 <listen_host>0.0.0.0</listen_host> #重啓 systemctl restart clickhouse-server # 增加dns解析 sudo vim /etc/hosts 172.16.0.103 ybchen-1 172.16.0.108 ybchen-2
- 網絡安全組記得開放http端口是8123,tcp端口是9000, 同步端口9009
高可用集羣架構-ClickHouse副本配置
#進入配置目錄 cd /etc/clickhouse-server #編輯配置文件 sudo vim config.xml #找到zookeeper節點,增加下面的,如果有多個zk則按照順序加即可 <zookeeper> <node> <host>172.16.0.103</host> <port>2181</port> </node> </zookeeper> #重啓 systemctl restart clickhouse-server ########建表####### #節點一,zk路徑一致,副本名稱不一樣 CREATE TABLE tb_product ( userId UInt32 ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/1/tb_product', 'product-replica-1') ORDER BY (userId) #節點二,zk路徑一致,副本名稱不一樣 CREATE TABLE tb_product ( userId UInt32 ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/1/tb_product', 'product-replica-2') ORDER BY (userId) 注意 副本只能同步數據,不能同步表結構,需要在每臺機器上手動建表 #插入和查詢數據 INSERT into tb_product values(1),(2),(3) select * from tb_product #查詢zk配置 select * from system.zookeeper where path='/'
表引擎-數據副本
ReplicatedMergeTree
語法
zoo_path
— zk 中該表的路徑,可自定義名稱,同一張表的同一分片的不同副本,要定義相同的路徑replica_name
— zk 中的該表的副本名稱,同一張表的同一分片的不同副本,要定義不同的名稱
CREATE TABLE tb_order
(
EventDate DateTime,
CounterID UInt32,
UserID UInt32
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/tb_order', 'tb_order_01')
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID)
表引擎-分佈式引擎
語法
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = Distributed(cluster, database, table[, sharding_key[, policy_name]])
[SETTINGS name=value, ...]
-
cluster:集羣名稱,與集羣配置中的自定義名稱相對應,比如 xdclass_shard。
-
database:數據庫名稱
-
table:本地表名稱
-
sharding_key:可選參數,用於分片的key值,在寫入的數據Distributed表引擎會依據分片key的規則,將數據分佈到各個節點的本地表
- user_id等業務字段、rand()隨機函數等規則
分片配置 config.xml
<remote_servers> <logs> <!-- 分佈式查詢的服務器間集羣密碼 默認值:無密碼(將不執行身份驗證) 如果設置了,那麼分佈式查詢將在分片上驗證,所以至少: - 這樣的集羣應該存在於shard上 - 這樣的集羣應該有相同的密碼。 而且(這是更重要的),initial_user將作爲查詢的當前用戶使用。 --> <!-- <secret></secret> --> <shard> <!-- 可選的。寫數據時分片權重。 默認: 1. --> <weight>1</weight> <!-- 可選的。是否只將數據寫入其中一個副本。默認值:false(將數據寫入所有副本)。 --> <internal_replication>false</internal_replication> <replica> <!-- 可選的。負載均衡副本的優先級,請參見(load_balancing 設置)。默認值:1(值越小優先級越高)。 --> <priority>1</priority> <host>example01-01-1</host> <port>9000</port> </replica> <replica> <host>example01-01-2</host> <port>9000</port> </replica> </shard> <shard> <weight>2</weight> <internal_replication>false</internal_replication> <replica> <host>example01-02-1</host> <port>9000</port> </replica> <replica> <host>example01-02-2</host> <secure>1</secure> <port>9440</port> </replica> </shard> </logs> </remote_servers>
這裏定義了一個名爲’logs’的集羣,它由兩個分片組成,每個分片包含兩個副本。 分片是指包含數據不同部分的服務器(要讀取所有數據,必須訪問所有分片)。 副本是存儲複製數據的服務器(要讀取所有數據,訪問任一副本上的數據即可)。
集羣名稱不能包含點號。
每個服務器需要指定 host
,port
,和可選的 user
,password
,secure
,compression
的參數:
host
– 遠程服務器地址。可以域名、IPv4或IPv6。如果指定域名,則服務在啓動時發起一個 DNS 請求,並且請求結果會在服務器運行期間一直被記錄。如果 DNS 請求失敗,則服務不會啓動。如果你修改了 DNS 記錄,則需要重啓服務。port
– 消息傳遞的 TCP 端口(「tcp_port」配置通常設爲 9000)。不要跟 http_port 混淆。user
– 用於連接遠程服務器的用戶名。默認值:default。該用戶必須有權限訪問該遠程服務器。訪問權限配置在 users.xml 文件中。更多信息,請查看«訪問權限»部分。password
– 用於連接遠程服務器的密碼。默認值:空字符串。secure
– 是否使用ssl進行連接,設爲true時,通常也應該設置port
= 9440。服務器也要監聽<tcp_port_secure>9440</tcp_port_secure>
並有正確的證書。compression
- 是否使用數據壓縮。默認值:true。
配置實戰
機器
服務類型 | 公網 | 私網 | hostname |
clickhouse-1 | 47.116.143.16 | 172.16.0.103 | ybchen-1 |
clickhouse-2 | 101.132.27.2 | 172.16.0.108 | ybchen-2 |
zookeeper | 47.116.143.16 | 172.16.0.103 | ybchen-1 |
每臺機器上的配置
#進入配置目錄 cd /etc/clickhouse-server #編輯配置文件 sudo vim config.xml <!-- 2shard 1replica --> <cluster_2shards_1replicas> <!-- shard1 --> <shard> <replica> <host>172.16.0.103</host> <port>9000</port> </replica> </shard> <!-- shard2 --> <shard> <replica> <host>172.16.0.108</host> <port> 9000</port> </replica> </shard> </cluster_2shards_1replicas> <zookeeper> <node > <host>172.16.0.103</host> <port>2181</port> </node> </zookeeper>
重啓
systemctl restart clickhouse-server
判斷是否配置成功
- 重啓ClickHouse前查詢,查不到對應的集羣名稱,重啓ClickHouse後能查詢到
select * from system.clusters
建表
#【選一個節點】創建好本地表後,在1個節點創建,會在其他節點都存在
create table default.ybchen_order on cluster cluster_2shards_1replicas
(id Int8,name String) engine =MergeTree order by id;
#【選一個節點】創建分佈式表名 ybchen_order_all,在1個節點創建,會在其他節點都存在
create table ybchen_order_all on cluster cluster_2shards_1replicas (
id Int8,name String
)engine = Distributed(cluster_2shards_1replicas,default, ybchen_order,hiveHash(id));
#分佈式表插入
insert into ybchen_order_all values(1,'老陳'),(2,'老王'),(3,'老李'),(4,'老趙');
#【任意節點查詢-分佈式,全部數據】
SELECT * from ybchen_order_all
#【任意本地節點查詢,部分數據】
SELECT * from ybchen_order
SpringBoot整合Clickhouse
數據準備
CREATE TABLE default.visit_stats
(
`product_id` UInt64,
`is_new` UInt16,
`province` String,
`city` String,
`pv` UInt32,
`visit_time` DateTime
)
ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(visit_time)
ORDER BY (
product_id,
is_new,
province,
city
);
創建工程
添加依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.13</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ybchen</groupId> <artifactId>ybchen-clickhouse</artifactId> <version>0.0.1-SNAPSHOT</version> <name>ybchen-clickhouse</name> <description>SpringBoot整合Clickhouse</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- clickhouse --> <dependency> <groupId>ru.yandex.clickhouse</groupId> <artifactId>clickhouse-jdbc</artifactId> <version>0.3.2</version> </dependency> <!--mybatis plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
配置類
server.port=8888
spring.datasource.driver-class-name=ru.yandex.clickhouse.ClickHouseDriver
spring.datasource.url=jdbc:clickhouse://47.116.143.16:8123/default
# 賬號
#spring.datasource.username=
# 密碼
#spring.datasource.password=
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.root=INFO
實體類
package com.ybchen.model; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; /** * @Description: * @Author:chenyanbin * @CreateTime: 2022-05-14 21:51 * @Version:1.0.0 */ @Data @TableName("visit_stats") public class VisitStatsDO { /** * 商品 */ private Long productId; /** * 1是新訪客,0是老訪客 */ private Integer isNew; /** * 省份 */ private String province; /** * 城市 */ private String city; /** * 訪問量 */ private Integer pv; /** * 訪問時間 */ private String visitTime; }
Mapper
package com.ybchen.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ybchen.model.VisitStatsDO; public interface VisitStatsMapper extends BaseMapper<VisitStatsDO> { }
Controller
package com.ybchen.controller; import com.ybchen.request.VisitRecordListRequest; import com.ybchen.service.VisitService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; /** * @Description: * @Author:chenyanbin * @CreateTime: 2022-05-14 21:53 * @Version:1.0.0 */ @RestController @RequestMapping("/api/v1/data") public class DataController { @Autowired VisitService visitService; @PostMapping("list") public Map<String,Object> queryVisitRecordList( @RequestBody VisitRecordListRequest request ) { return visitService.queryVisitRecordList(request); } }
package com.ybchen.request; import lombok.Data; /** * @Description: * @Author:chenyanbin * @CreateTime: 2022-05-14 21:54 * @Version:1.0.0 */ @Data public class VisitRecordListRequest { private Long productId; private int page; private int size; }
Service
package com.ybchen.service; import com.ybchen.request.VisitRecordListRequest; import java.util.Map; public interface VisitService { /** * 查詢訪問記錄 * @param request * @return */ Map<String, Object> queryVisitRecordList(VisitRecordListRequest request); }
package com.ybchen.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ybchen.mapper.VisitStatsMapper; import com.ybchen.model.VisitStatsDO; import com.ybchen.request.VisitRecordListRequest; import com.ybchen.service.VisitService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; /** * @Description: * @Author:chenyanbin * @CreateTime: 2022-05-14 21:55 * @Version:1.0.0 */ @Service @Slf4j public class VisitServiceImpl implements VisitService { @Autowired VisitStatsMapper visitStatsMapper; @Override public Map<String, Object> queryVisitRecordList(VisitRecordListRequest request) { Map<String, Object> dataMap = new HashMap<>(3); IPage<VisitStatsDO> pageInfo = new Page<>(request.getPage(), request.getSize()); IPage<VisitStatsDO> visitStatsDOIPage = visitStatsMapper.selectPage( pageInfo, new LambdaQueryWrapper<VisitStatsDO>() .eq(VisitStatsDO::getProductId, request.getProductId()) ); /** * 分頁記錄列表 */ dataMap.put("current_data",visitStatsDOIPage.getRecords()); /** * 當前分頁總頁數 */ dataMap.put("total_page",visitStatsDOIPage.getPages()); /** * 總記錄數 */ dataMap.put("total_record",visitStatsDOIPage.getTotal()); return dataMap; } }
測試
Clickhouse語法
Clickhouse語法和mysql差不多,只不過Clickhouse提供了更加豐富的函數,具體請查閱官網文檔:點我直達