clickhouse數據實時更新實現的三種方式


一、UPDATE+Optimize方式

--建表及插入數據

CREATE TABLE tb_test(
ts DateTime,
uid String,
biz String
) ENGINE = MergeTree() ORDER BY (ts) SETTINGS index_granularity = 8192;

INSERT INTO tb_test VALUES ('2019-06-07 20:01:01', 'a', 'a1');
INSERT INTO tb_test VALUES ('2019-06-07 20:01:01', 'b', 'b1');
INSERT INTO tb_test VALUES ('2019-06-07 20:01:01', 'c', 'c1');

--執行更新

alter table tb_test update biz = 'ccccc' where uid = 'c';
optimize table tb_test;

 

物理文件的前后變化

剛插入時,因為是執行了三次insert,所以生成了3個part

drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:16 all_1_1_0
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:16 all_2_2_0
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:16 all_3_3_0
drwxr-x--- 2 clickhouse clickhouse   6 3月   7 14:15 detached
-rw-r----- 1 clickhouse clickhouse   1 3月   7 14:15 format_version.txt

 

執行alter和optimize后

update字段的值就對應着mutation操作,可以看到part(all_1_1_0、all_2_2_0、all_3_3_0)每個都執行了mutation,生成了新的part(all_1_1_0_4、all_2_2_0_4、all_3_3_0_4),后面又執行了merge合並成了all_1_3_1_4這個part。

drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:16 all_1_1_0
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:17 all_1_1_0_4
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:16 all_2_2_0
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:17 all_2_2_0_4
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:16 all_3_3_0
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:17 all_3_3_0_4
drwxr-x--- 2 clickhouse clickhouse   6 3月   7 14:15 detached
-rw-r----- 1 clickhouse clickhouse   1 3月   7 14:15 format_version.txt
-rw-r----- 1 clickhouse clickhouse 102 3月   7 14:17 mutation_4.txt

 

由於clickhouse是異步刪除的,所以過程中還會有part(all_1_1_0、all_2_2_0、all_3_3_0)短時間存在,但最終會變為如下文件

drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:18 all_1_3_1_4
drwxr-x--- 2 clickhouse clickhouse   6 3月   7 14:15 detached
-rw-r----- 1 clickhouse clickhouse   1 3月   7 14:15 format_version.txt
-rw-r----- 1 clickhouse clickhouse 102 3月   7 14:17 mutation_4.txt

 

如果在執行了alter和optimize后沒有上述變化,可以將optimize語句final

optimize table tb_test final;


由於本次數據壓力較小,所以mutation很快執行完了,但也不影響final的實驗效果,執行完上述sql后,clickhouse會強制part更新,並且是同步操作,直至成功或失敗。

drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:18 all_1_3_1_4
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:19 all_1_3_2_4
drwxr-x--- 2 clickhouse clickhouse   6 3月   7 14:15 detached
-rw-r----- 1 clickhouse clickhouse   1 3月   7 14:15 format_version.txt
-rw-r----- 1 clickhouse clickhouse 102 3月   7 14:17 mutation_4.txt

 

可以發現,新生成了all_1_3_2_4(all_{min}_{max}_{merge_verson}_{mutation_version}),雖然min和max沒變,但是merge_verson加一了,並真正生成了新的part。

all_1_3_1_4后續也會被刪掉,最終形態如下

drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:19 all_1_3_2_4
drwxr-x--- 2 clickhouse clickhouse   6 3月   7 14:15 detached
-rw-r----- 1 clickhouse clickhouse   1 3月   7 14:15 format_version.txt
-rw-r----- 1 clickhouse clickhouse 102 3月   7 14:17 mutation_4.txt

 

注意點:
利用該方法可以讀取到最新的數據,但是是建立在強制clickhouse去做新part的生成去代替老part,如果part非常多,optimize的耗時會非常長甚至失敗,可以根據實際情況和partiton的分布使數據的更新只涉及部分part,可以提高效率。

二、UPDATE+ SETTING mutations_sync方式

  mutations_sync有3種配置,默認為0,即所有的mutation都為異步操作;為1,表示等待當前節點完成mutation操作;為2,表示等待所有節點都完成mutation操作。如果是MergeTree只會判斷0或非0,如果是ReplicatedMergeTree才會支持1和2的配置

同樣借助上面的表

alter table tb_test update biz = 'ddddd' where uid = 'c' settings mutations_sync = 1


物理文件變化

執行完上述sql就會生成新的part(all_1_3_2_5),並且mutation版本加一

drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:19 all_1_3_2_4
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:29 all_1_3_2_5
drwxr-x--- 2 clickhouse clickhouse   6 3月   7 14:15 detached
-rw-r----- 1 clickhouse clickhouse   1 3月   7 14:15 format_version.txt
-rw-r----- 1 clickhouse clickhouse 102 3月   7 14:17 mutation_4.txt
-rw-r----- 1 clickhouse clickhouse 102 3月   7 14:29 mutation_5.txt

 

最終會刪掉all_1_3_2_4,變為如下狀態

drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:29 all_1_3_2_5
drwxr-x--- 2 clickhouse clickhouse   6 3月   7 14:15 detached
-rw-r----- 1 clickhouse clickhouse   1 3月   7 14:15 format_version.txt
-rw-r----- 1 clickhouse clickhouse 102 3月   7 14:17 mutation_4.txt
-rw-r----- 1 clickhouse clickhouse 102 3月   7 14:29 mutation_5.txt

 

注意點:
該方法不需要執行optimize,但原理上還是通過新生成part去代替老part后才能提供新的准確的數據。如果part非常多同樣會遇到第一種方式的問題。clickhouse在執行同步操作時可能會因為各種原因失敗,但是該方法還是會在后台繼續進行更新,具體情況可以根據system.mutations中的記錄判斷。

三、INSERT+Final方式

這種方法需要ReplacingMergeTree表引擎配置使用

CREATE TABLE tb_test_replacing(
ts DateTime,
uid String,
biz String
) ENGINE = ReplacingMergeTree(ts) ORDER BY (ts) SETTINGS index_granularity = 8192;

INSERT INTO tb_test_replacing VALUES ('2019-06-07 20:01:01', 'c', 'c1');

 

這種方式就不用執行alter了,而是以insert的形式來代替alter操作,即每次select時都是取最新的一條數據,sql語句如下

INSERT INTO tb_test_replacing VALUES ('2019-06-07 20:01:01', 'c', 'c2');


此時查詢該表,發現還是兩條數據,並沒有實現更新

SELECT *
FROM tb_test_replacing

Query id: 02cfecf5-18f1-4f9f-ad55-e7b596935de8

┌──────────────────ts─┬─uid─┬─biz─┐
│ 2019-06-07 20:01:01 │ c   │ c2  │
└─────────────────────┴─────┴─────┘
┌──────────────────ts─┬─uid─┬─biz─┐
│ 2019-06-07 20:01:01 │ c   │ c1  │
└─────────────────────┴─────┴─────┘

2 rows in set. Elapsed: 0.004 sec.

 

在sql后面加上final,發現只返回了最新的一條,是符合預期的

SELECT *
FROM tb_test_replacing
FINAL

Query id: 614c7e93-48c2-4129-a734-c8a0dd722fcd

┌──────────────────ts─┬─uid─┬─biz─┐
│ 2019-06-07 20:01:01 │ c   │ c2  │
└─────────────────────┴─────┴─────┘

1 rows in set. Elapsed: 0.007 sec.

 

ReplacingMergeTree在建表時可以看到ENGINE = ReplacingMergeTree(ts),其中的ts就是版本信息,clickhouse會每次插入記錄版本,就是依據這個字段,在查詢時會返回最后最新的版本數據。所以第二個insert的ts和第一個insert的ts字段的值一樣,所以會以第二條記錄為准,即實現了更新。具體用法見:ReplacingMergeTree

這時物理文件並沒有發生實質的合並

drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:32 all_1_1_0
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:32 all_2_2_0
drwxr-x--- 2 clickhouse clickhouse   6 3月   7 14:32 detached
-rw-r----- 1 clickhouse clickhouse   1 3月   7 14:32 format_version.txt

 

這時如果再執行optimize,就會發生真正的merge,生成了all_1_2_1,后續all_1_1_0、all_2_2_0會被異步刪除

drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:32 all_1_1_0
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:35 all_1_2_1
drwxr-x--- 2 clickhouse clickhouse 152 3月   7 14:32 all_2_2_0
drwxr-x--- 2 clickhouse clickhouse   6 3月   7 14:32 detached
-rw-r----- 1 clickhouse clickhouse   1 3月   7 14:32 format_version.txt

 

再查詢該表,不加final

SELECT *
FROM tb_test_replacing

Query id: 14ae82a1-7ce3-4b92-918b-b9f2496f3034

┌──────────────────ts─┬─uid─┬─biz─┐
│ 2019-06-07 20:01:01 │ c   │ c2  │
└─────────────────────┴─────┴─────┘

1 rows in set. Elapsed: 0.003 sec. 

 

可知,ReplacingMergeTree在進行merge時是會進行去重的,最終只會保留最新版本數據。

注意點
這里要注意,ReplacingMergeTree是根據orderby做去重的,而不是根據primarykey。舉個例子:

CREATE TABLE tb_test_replacing2
(
`ts` DateTime,
`uid` String,
`biz` String
)
ENGINE = ReplacingMergeTree(ts)
PRIMARY KEY uid
ORDER BY (uid, ts)
SETTINGS index_granularity = 8192;

INSERT INTO tb_test_replacing2 VALUES ('2019-06-07 20:01:01', 'a', 'a1');
INSERT INTO tb_test_replacing2 VALUES ('2019-06-07 20:01:02', 'a', 'b1');

 

進行final的查詢,會發現並沒有根據主鍵(primarykey)去重,而是兩條數據都查出來了,因為雖然兩條記錄的主鍵一樣,但是ts字段不一樣,一個是20:01:01,另一個是20:01:02。

SELECT *
FROM tb_test_replacing2
FINAL

Query id: e70e0d00-0ae0-4e42-bcbd-09b852467ce5

┌──────────────────ts─┬─uid─┬─biz─┐
│ 2019-06-07 20:01:02 │ a   │ b1  │
└─────────────────────┴─────┴─────┘
┌──────────────────ts─┬─uid─┬─biz─┐
│ 2019-06-07 20:01:01 │ a   │ a1  │
└─────────────────────┴─────┴─────┘

2 rows in set. Elapsed: 0.007 sec. 

 

既然是知道是這個規則,那再插入一條數據,保證uid和ts值都一樣,biz換為a2

INSERT INTO tb_test_replacing2 VALUES ('2019-06-07 20:01:01', 'a', 'a2');
--再次查詢,可以發現只顯示最新一條記錄了,符合預期

SELECT *
FROM tb_test_replacing2
FINAL

Query id: da9b8464-e7bc-4a4f-97db-9e4304513f63

┌──────────────────ts─┬─uid─┬─biz─┐
│ 2019-06-07 20:01:02 │ a   │ b1  │
└─────────────────────┴─────┴─────┘
┌──────────────────ts─┬─uid─┬─biz─┐
│ 2019-06-07 20:01:01 │ a   │ a2  │
└─────────────────────┴─────┴─────┘

2 rows in set. Elapsed: 0.007 sec. 
 
        

 

在原理上,使用select final是將數據讀取后在內存排序才能根據orderby鍵找到最新的一條記錄,雖然物理文件不需要做merge但是在內存中也做了類似merge的方式,會有性能損耗。

總結
綜上所述,上述三種方法各有各的特點,使用時可以根據業務特點(實時數據、離線數據、T+1數據等)進行更適合的方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM