在上一篇分享中,咱們介紹了ClickHouse的安裝部署和簡單使用。本文將介紹ClickHouse中一個很是重要的概念—表引擎(table engine)。若是對MySQL熟悉的話,或許你應該據說過InnoDB和MyISAM存儲引擎。不一樣的存儲引擎提供不一樣的存儲機制、索引方式、鎖定水平等功能,也能夠稱之爲表類型。ClickHouse提供了豐富的表引擎,這些不一樣的表引擎也表明着不一樣的表類型。好比數據表擁有何種特性、數據以何種形式被存儲以及如何被加載。本文會對ClickHouse中常見的表引擎進行介紹,主要包括如下內容:java
舒適提示:本文內容較長,建議收藏
引擎分類 | 引擎名稱 |
---|---|
MergeTree系列 | MergeTree 、ReplacingMergeTree 、SummingMergeTree 、 AggregatingMergeTree CollapsingMergeTree 、 VersionedCollapsingMergeTree 、GraphiteMergeTree |
Log系列 | TinyLog 、StripeLog 、Log |
Integration Engines | Kafka 、MySQL、ODBC 、JDBC、HDFS |
Special Engines | Distributed 、MaterializedView、 Dictionary 、Merge 、File、Null 、Set 、Join 、 URL View、Memory 、 Buffer |
Log系列表引擎功能相對簡單,主要用於快速寫入小表(1百萬行左右的表),而後所有讀出的場景。即一次寫入屢次查詢。mysql
TinyLog是Log系列引擎中功能簡單、性能較低的引擎。它的存儲結構由數據文件和元數據兩部分組成。其中,數據文件是按列獨立存儲的,也就是說每個列字段都對應一個文件。除此以外,TinyLog不支持併發數據讀取。git
該引擎適用於一次寫入,屢次讀取的場景。對於處理小批數據的中間表可使用該引擎。值得注意的是,使用大量的小表存儲數據,性能會很低。github
CREATE TABLE emp_tinylog ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資' )ENGINE=TinyLog(); INSERT INTO emp_tinylog VALUES (1,'tom','上海',25,'技術部',20000),(2,'jack','上海',26,'人事部',10000); INSERT INTO emp_tinylog VALUES (3,'bob','北京',33,'財務部',50000),(4,'tony','杭州',28,'銷售事部',50000);
進入默認數據存儲目錄,查看底層數據存儲形式,能夠看出:TinyLog引擎表每一列都對應的文件sql
[root@cdh04 emp_tinylog]# pwd /var/lib/clickhouse/data/default/emp_tinylog [root@cdh04 emp_tinylog]# ll 總用量 28 -rw-r----- 1 clickhouse clickhouse 56 9月 17 14:33 age.bin -rw-r----- 1 clickhouse clickhouse 97 9月 17 14:33 depart.bin -rw-r----- 1 clickhouse clickhouse 60 9月 17 14:33 emp_id.bin -rw-r----- 1 clickhouse clickhouse 70 9月 17 14:33 name.bin -rw-r----- 1 clickhouse clickhouse 68 9月 17 14:33 salary.bin -rw-r----- 1 clickhouse clickhouse 185 9月 17 14:33 sizes.json -rw-r----- 1 clickhouse clickhouse 80 9月 17 14:33 work_place.bin ## 查看sizes.json數據 ## 在sizes.json文件內使用JSON格式記錄了每一個.bin文件內對應的數據大小的信息 { "yandex":{ "age%2Ebin":{ "size":"56" }, "depart%2Ebin":{ "size":"97" }, "emp_id%2Ebin":{ "size":"60" }, "name%2Ebin":{ "size":"70" }, "salary%2Ebin":{ "size":"68" }, "work_place%2Ebin":{ "size":"80" } } }
當咱們執行ALTER操做時會報錯,說明該表引擎不支持ALTER操做數據庫
-- 如下操做會報錯: -- DB::Exception: Mutations are not supported by storage TinyLog. ALTER TABLE emp_tinylog DELETE WHERE emp_id = 5; ALTER TABLE emp_tinylog UPDATE age = 30 WHERE emp_id = 4;
相比TinyLog而言,StripeLog擁有更高的查詢性能(擁有.mrk標記文件,支持並行查詢),同時其使用了更少的文件描述符(全部數據使用同一個文件保存)。json
CREATE TABLE emp_stripelog ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資' )ENGINE=StripeLog; -- 插入數據 INSERT INTO emp_stripelog VALUES (1,'tom','上海',25,'技術部',20000),(2,'jack','上海',26,'人事部',10000); INSERT INTO emp_stripelog VALUES (3,'bob','北京',33,'財務部',50000),(4,'tony','杭州',28,'銷售事部',50000); -- 查詢數據 -- 因爲是分兩次插入數據,因此查詢時會有兩個數據塊 cdh04 :) select * from emp_stripelog; SELECT * FROM emp_stripelog ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 3 │ bob │ 北京 │ 33 │ 財務部 │ 50000.00 │ │ 4 │ tony │ 杭州 │ 28 │ 銷售事部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴──────────┴──────────┘
進入默認數據存儲目錄,查看底層數據存儲形式bash
[root@cdh04 emp_stripelog]# pwd /var/lib/clickhouse/data/default/emp_stripelog [root@cdh04 emp_stripelog]# ll 總用量 12 -rw-r----- 1 clickhouse clickhouse 673 9月 17 15:11 data.bin -rw-r----- 1 clickhouse clickhouse 281 9月 17 15:11 index.mrk -rw-r----- 1 clickhouse clickhouse 69 9月 17 15:11 sizes.json
能夠看出StripeLog表引擎對應的存儲結構包括三個文件:服務器
提示:
StripeLog
引擎將全部數據都存儲在了一個文件中,對於每次的INSERT操做,ClickHouse會將數據塊追加到表文件的末尾多線程StripeLog引擎一樣不支持
ALTER UPDATE
和ALTER DELETE
操做
Log引擎表適用於臨時數據,一次性寫入、測試場景。Log引擎結合了TinyLog表引擎和StripeLog表引擎的長處,是Log系列引擎中性能最高的表引擎。
CREATE TABLE emp_log ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資' )ENGINE=Log; INSERT INTO emp_log VALUES (1,'tom','上海',25,'技術部',20000),(2,'jack','上海',26,'人事部',10000); INSERT INTO emp_log VALUES (3,'bob','北京',33,'財務部',50000),(4,'tony','杭州',28,'銷售事部',50000); -- 查詢數據, -- 因爲是分兩次插入數據,因此查詢時會有兩個數據塊 cdh04 :) select * from emp_log; SELECT * FROM emp_log ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 3 │ bob │ 北京 │ 33 │ 財務部 │ 50000.00 │ │ 4 │ tony │ 杭州 │ 28 │ 銷售事部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴──────────┴──────────┘
進入默認數據存儲目錄,查看底層數據存儲形式
[root@cdh04 emp_log]# pwd /var/lib/clickhouse/data/default/emp_log [root@cdh04 emp_log]# ll 總用量 32 -rw-r----- 1 clickhouse clickhouse 56 9月 17 15:55 age.bin -rw-r----- 1 clickhouse clickhouse 97 9月 17 15:55 depart.bin -rw-r----- 1 clickhouse clickhouse 60 9月 17 15:55 emp_id.bin -rw-r----- 1 clickhouse clickhouse 192 9月 17 15:55 __marks.mrk -rw-r----- 1 clickhouse clickhouse 70 9月 17 15:55 name.bin -rw-r----- 1 clickhouse clickhouse 68 9月 17 15:55 salary.bin -rw-r----- 1 clickhouse clickhouse 216 9月 17 15:55 sizes.json -rw-r----- 1 clickhouse clickhouse 80 9月 17 15:55 work_place.bin
Log引擎的存儲結構包含三部分:
提示:Log表引擎會將每一列都存在一個文件中,對於每一次的INSERT操做,都會對應一個數據塊
在全部的表引擎中,最爲核心的當屬MergeTree系列表引擎,這些表引擎擁有最爲強大的性能和最普遍的使用場合。對於非MergeTree系列的其餘引擎而言,主要用於特殊用途,場景相對有限。而MergeTree系列表引擎是官方主推的存儲引擎,支持幾乎全部ClickHouse核心功能。
MergeTree在寫入一批數據時,數據總會以數據片斷的形式寫入磁盤,且數據片斷不可修改。爲了不片斷過多,ClickHouse會經過後臺線程,按期合併這些數據片斷,屬於相同分區的數據片斷會被合成一個新的片斷。這種數據片斷往復合併的特色,也正是合併樹名稱的由來。
MergeTree做爲家族系列最基礎的表引擎,主要有如下特色:
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, ...]
SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID))
。可選。CREATE TABLE emp_mergetree ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資' )ENGINE=MergeTree() ORDER BY emp_id PARTITION BY work_place ; -- 插入數據 INSERT INTO emp_mergetree VALUES (1,'tom','上海',25,'技術部',20000),(2,'jack','上海',26,'人事部',10000); INSERT INTO emp_mergetree VALUES (3,'bob','北京',33,'財務部',50000),(4,'tony','杭州',28,'銷售事部',50000); -- 查詢數據 -- 按work_place進行分區 cdh04 :) select * from emp_mergetree; SELECT * FROM emp_mergetree ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 3 │ bob │ 北京 │ 33 │ 財務部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 4 │ tony │ 杭州 │ 28 │ 銷售事部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴──────────┴──────────┘
查看一下數據存儲格式,能夠看出,存在三個分區文件夾,每個分區文件夾內存儲了對應分區的數據。
[root@cdh04 emp_mergetree]# pwd /var/lib/clickhouse/data/default/emp_mergetree [root@cdh04 emp_mergetree]# ll 總用量 16 drwxr-x--- 2 clickhouse clickhouse 4096 9月 17 17:45 1c89a3ba9fe5fd53379716a776c5ac34_3_3_0 drwxr-x--- 2 clickhouse clickhouse 4096 9月 17 17:44 40d45822dbd7fa81583d715338929da9_1_1_0 drwxr-x--- 2 clickhouse clickhouse 4096 9月 17 17:45 a6155dcc1997eda1a348cd98b17a93e9_2_2_0 drwxr-x--- 2 clickhouse clickhouse 6 9月 17 17:43 detached -rw-r----- 1 clickhouse clickhouse 1 9月 17 17:43 format_version.txt
進入一個分區目錄查看
columns.txt:列信息文件,使用明文格式存儲。用於保存此數據分區下的列字段信息,例如
[root@cdh04 1c89a3ba9fe5fd53379716a776c5ac34_3_3_0]# cat columns.txt columns format version: 1 6 columns: `emp_id` UInt16 `name` String `work_place` String `age` UInt8 `depart` String `salary` Decimal(9, 2)
.bin
數據文件,並以列字段名稱命名。.bin
文件中數據的偏移量信息-- 新插入兩條數據 cdh04 :) INSERT INTO emp_mergetree VALUES (5,'robin','北京',35,'財務部',50000),(6,'lilei','北京',38,'銷售事部',50000); -- 查詢結果 cdh04 :) select * from emp_mergetree; ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 3 │ bob │ 北京 │ 33 │ 財務部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name──┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 5 │ robin │ 北京 │ 35 │ 財務部 │ 50000.00 │ │ 6 │ lilei │ 北京 │ 38 │ 銷售事部 │ 50000.00 │ └────────┴───────┴────────────┴─────┴──────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 4 │ tony │ 杭州 │ 28 │ 銷售事部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴──────────┴──────────┘
能夠看出,新插入的數據新生成了一個數據塊,並無與原來的分區數據在一塊兒,咱們能夠執行optimize命令,執行合併操做
-- 執行合併操做 cdh04 :) OPTIMIZE TABLE emp_mergetree PARTITION '北京'; -- 再次執行查詢 cdh04 :) select * from emp_mergetree; SELECT * FROM emp_mergetree ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name──┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 3 │ bob │ 北京 │ 33 │ 財務部 │ 50000.00 │ │ 5 │ robin │ 北京 │ 35 │ 財務部 │ 50000.00 │ │ 6 │ lilei │ 北京 │ 38 │ 銷售事部 │ 50000.00 │ └────────┴───────┴────────────┴─────┴──────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 4 │ tony │ 杭州 │ 28 │ 銷售事部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴──────────┴──────────┘
執行上面的合併操做以後,會新生成一個該分區的文件夾,原理的分區文件夾不變。
-- 插入一條相同主鍵的數據 INSERT INTO emp_mergetree VALUES (1,'sam','杭州',35,'財務部',50000); -- 會發現該條數據能夠插入,由此可知,並不會對主鍵進行去重
上文提到MergeTree表引擎沒法對相同主鍵的數據進行去重,ClickHouse提供了ReplacingMergeTree引擎,能夠針對相同主鍵的數據進行去重,它可以在合併分區時刪除重複的數據。值得注意的是,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] [PRIMARY KEY expr] [SAMPLE BY expr] [SETTINGS name=value, ...]
CREATE TABLE emp_replacingmergetree ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資' )ENGINE=ReplacingMergeTree() ORDER BY emp_id PRIMARY KEY emp_id PARTITION BY work_place ; -- 插入數據 INSERT INTO emp_replacingmergetree VALUES (1,'tom','上海',25,'技術部',20000),(2,'jack','上海',26,'人事部',10000); INSERT INTO emp_replacingmergetree VALUES (3,'bob','北京',33,'財務部',50000),(4,'tony','杭州',28,'銷售事部',50000);
當咱們再次向該表插入具備相同主鍵的數據時,觀察查詢數據的變化
INSERT INTO emp_replacingmergetree VALUES (1,'tom','上海',25,'技術部',50000); -- 查詢數據,因爲沒有進行合併,因此存在主鍵重複的數據 cdh04 :) select * from emp_replacingmergetree; SELECT * FROM emp_replacingmergetree ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 3 │ bob │ 北京 │ 33 │ 財務部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 4 │ tony │ 杭州 │ 28 │ 銷售事部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴──────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ -- 執行合併操做 optimize table emp_replacingmergetree final; -- 再次查詢,相同主鍵的數據,保留最近插入的數據,舊的數據被清除 cdh04 :) select * from emp_replacingmergetree; SELECT * FROM emp_replacingmergetree ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 50000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 4 │ tony │ 杭州 │ 28 │ 銷售事部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴──────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 3 │ bob │ 北京 │ 33 │ 財務部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘
從上面的示例中能夠看出,ReplacingMergeTree是支持對數據去重的,那麼是根據什麼進行去重呢?答案是:ReplacingMergeTree在去除重複數據時,是以ORDERBY排序鍵爲基準的,而不是PRIMARY KEY。咱們在看一個示例:
CREATE TABLE emp_replacingmergetree1 ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資' )ENGINE=ReplacingMergeTree() ORDER BY (emp_id,name) -- 注意排序key是兩個字段 PRIMARY KEY emp_id -- 主鍵是一個字段 PARTITION BY work_place ; -- 插入數據 INSERT INTO emp_replacingmergetree1 VALUES (1,'tom','上海',25,'技術部',20000),(2,'jack','上海',26,'人事部',10000); INSERT INTO emp_replacingmergetree1 VALUES (3,'bob','北京',33,'財務部',50000),(4,'tony','杭州',28,'銷售事部',50000);
再次向該表中插入相同emp_id和name的數據,並執行合併操做,再觀察數據
-- 插入數據 INSERT INTO emp_replacingmergetree1 VALUES (1,'tom','上海',25,'技術部',50000),(1,'sam','上海',25,'技術部',20000); -- 執行合併操做 optimize table emp_replacingmergetree1 final; -- 再次查詢,可見相同的emp_id和name數據被去重,而形同的主鍵emp_id不會去重 -- ReplacingMergeTree在去除重複數據時,是以ORDERBY排序鍵爲基準的,而不是PRIMARY KEY cdh04 :) select * from emp_replacingmergetree1; SELECT * FROM emp_replacingmergetree1 ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 3 │ bob │ 北京 │ 33 │ 財務部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ sam │ 上海 │ 25 │ 技術部 │ 20000.00 │ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 50000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 4 │ tony │ 杭州 │ 28 │ 銷售事部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴──────────┴──────────┘
至此,咱們知道了ReplacingMergeTree是支持去重的,而且是按照ORDERBY排序鍵爲基準進行去重的。細心的你會發現,上面的重複數據是在一個分區內的,那麼若是重複的數據不在一個分區內,會發生什麼現象呢?咱們再次向上面的emp_replacingmergetree1表插入不一樣分區的重複數據
-- 插入數據 INSERT INTO emp_replacingmergetree1 VALUES (1,'tom','北京',26,'技術部',10000); -- 執行合併操做 optimize table emp_replacingmergetree1 final; -- 再次查詢 -- 發現 1 │ tom │ 北京 │ 26 │ 技術部 │ 10000.00 -- 與 1 │ tom │ 上海 │ 25 │ 技術部 │ 50000.00 -- 數據重複,由於這兩行數據不在同一個分區內 -- 這是由於ReplacingMergeTree是以分區爲單位刪除重複數據的。 -- 只有在相同的數據分區內重複的數據才能夠被刪除,而不一樣數據分區之間的重複數據依然不能被剔除 cdh04 :) select * from emp_replacingmergetree1; SELECT * FROM emp_replacingmergetree1 ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 北京 │ 26 │ 技術部 │ 10000.00 │ │ 3 │ bob │ 北京 │ 33 │ 財務部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ sam │ 上海 │ 25 │ 技術部 │ 20000.00 │ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 50000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 4 │ tony │ 杭州 │ 28 │ 銷售事部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴──────────┴──────────┘
ReplacingMergeTree在去除重複數據時,是以ORDERBY排序鍵爲基準的,而不是PRIMARY KEY。
在執行分區合併時,會觸發刪除重複數據。optimize的合併操做是在後臺執行的,沒法預測具體執行時間點,除非是手動執行。
ReplacingMergeTree是以分區爲單位刪除重複數據的。只有在相同的數據分區內重複的數據才能夠被刪除,而不一樣數據分區之間的重複數據依然不能被剔除。
若是沒有設置[ver]版本號,則保留同一組重複數據中的最新插入的數據; 若是設置了[ver]版本號,則保留同一組重複數據中ver字段取值最大的那一行。
通常在數據量比較大的狀況,儘可能不要使用該命令。由於在海量數據場景下,執行optimize要消耗大量時間
該引擎繼承了MergeTree引擎,當合並 SummingMergeTree
表的數據片斷時,ClickHouse 會把全部具備相同主鍵的行合併爲一行,該行包含了被合併的行中具備數值數據類型的列的彙總值,即若是存在重複的數據,會對對這些重複的數據進行合併成一條數據,相似於group by的效果。
推薦將該引擎和 MergeTree
一塊兒使用。例如,將完整的數據存儲在 MergeTree
表中,而且使用 SummingMergeTree
來存儲聚合數據。這種方法能夠避免由於使用不正確的主鍵組合方式而丟失數據。
若是用戶只須要查詢數據的彙總結果,不關心明細數據,而且數據的彙總條件是預先明確的,即GROUP BY的分組字段是肯定的,可使用該表引擎。
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, ...]
CREATE TABLE emp_summingmergetree ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資' )ENGINE=SummingMergeTree(salary) ORDER BY (emp_id,name) -- 注意排序key是兩個字段 PRIMARY KEY emp_id -- 主鍵是一個字段 PARTITION BY work_place ; -- 插入數據 INSERT INTO emp_summingmergetree VALUES (1,'tom','上海',25,'技術部',20000),(2,'jack','上海',26,'人事部',10000); INSERT INTO emp_summingmergetree VALUES (3,'bob','北京',33,'財務部',50000),(4,'tony','杭州',28,'銷售事部',50000);
當咱們再次插入具備相同emp_id,name的數據時,觀察結果
INSERT INTO emp_summingmergetree VALUES (1,'tom','上海',25,'信息部',10000),(1,'tom','北京',26,'人事部',10000); cdh04 :) select * from emp_summingmergetree; -- 查詢 SELECT * FROM emp_summingmergetree ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 3 │ bob │ 北京 │ 33 │ 財務部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 4 │ tony │ 杭州 │ 28 │ 銷售事部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴──────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 北京 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 信息部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ -- 執行合併操做 optimize table emp_summingmergetree final; cdh04 :) select * from emp_summingmergetree; -- 再次查詢,新插入的數據 1 │ tom │ 上海 │ 25 │ 信息部 │ 10000.00 -- 原來的數據 : 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 -- 這兩行數據合併成: 1 │ tom │ 上海 │ 25 │ 技術部 │ 30000.00 SELECT * FROM emp_summingmergetree ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 北京 │ 26 │ 人事部 │ 10000.00 │ │ 3 │ bob │ 北京 │ 33 │ 財務部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 30000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐ │ 4 │ tony │ 杭州 │ 28 │ 銷售事部 │ 50000.00 │ └────────┴──────┴────────────┴─────┴──────────┴──────────┘
要保證PRIMARY KEY expr指定的主鍵是ORDER BY expr 指定字段的前綴,好比
-- 容許 ORDER BY (A,B,C) PRIMARY KEY A -- 會報錯 -- DB::Exception: Primary key must be a prefix of the sorting key ORDER BY (A,B,C) PRIMARY KEY B
這種強制約束保障了即使在二者定義不一樣的狀況下,主鍵仍然是排序鍵的前綴,不會出現索引與數據順序混亂的問題。
用ORBER BY排序鍵做爲聚合數據的條件Key。即若是排序key是相同的,則會合併成一條數據,並對指定的合併字段進行聚合。
以數據分區爲單位來聚合數據。當分區合併時,同一數據分區內聚合Key相同的數據會被合併彙總,而不一樣分區之間的數據則不會被彙總。
若是沒有指定聚合字段,則會按照非主鍵的數值類型字段進行聚合
若是兩行數據除了排序字段相同,其餘的非聚合字段不相同,那麼在聚合發生時,會保留最初的那條數據,新插入的數據對應的那個字段值會被捨棄
-- 新插入的數據: 1 │ tom │ 上海 │ 25 │ 信息部 │ 10000.00 -- 最初的數據 : 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 -- 聚合合併的結果: 1 │ tom │ 上海 │ 25 │ 技術部 │ 30000.00
該表引擎繼承自MergeTree,可使用 AggregatingMergeTree
表來作增量數據統計聚合。若是要按一組規則來合併減小行數,則使用 AggregatingMergeTree
是合適的。AggregatingMergeTree是經過預先定義的聚合函數計算數據並經過二進制的格式存入表內。
與SummingMergeTree的區別在於:SummingMergeTree對非主鍵列進行sum聚合,而AggregatingMergeTree則能夠指定各類聚合函數。
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] ( name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1], name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2], ... ) ENGINE = AggregatingMergeTree() [PARTITION BY expr] [ORDER BY expr] [SAMPLE BY expr] [SETTINGS name=value, ...]
CREATE TABLE emp_aggregatingmergeTree ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary AggregateFunction(sum,Decimal32(2)) COMMENT '工資' )ENGINE=AggregatingMergeTree() ORDER BY (emp_id,name) -- 注意排序key是兩個字段 PRIMARY KEY emp_id -- 主鍵是一個字段 PARTITION BY work_place ;
對於AggregateFunction類型的列字段,在進行數據的寫入和查詢時與其餘的表引擎有很大區別,在寫入數據時,須要調用<agg>-State函數;而在查詢數據時,則須要調用相應的<agg>-Merge函數。對於上面的建表語句而言,須要使用sumState函數進行數據插入
-- 插入數據, -- 注意:須要使用INSERT…SELECT語句進行數據插入 INSERT INTO TABLE emp_aggregatingmergeTree SELECT 1,'tom','上海',25,'信息部',sumState(toDecimal32(10000,2)); INSERT INTO TABLE emp_aggregatingmergeTree SELECT 1,'tom','上海',25,'信息部',sumState(toDecimal32(20000,2)); -- 查詢數據 SELECT emp_id, name , sumMerge(salary) FROM emp_aggregatingmergeTree GROUP BY emp_id,name; -- 結果輸出 ┌─emp_id─┬─name─┬─sumMerge(salary)─┐ │ 1 │ tom │ 30000.00 │ └────────┴──────┴──────────────────┘
上面演示的用法很是的麻煩,其實更多的狀況下,咱們能夠結合物化視圖一塊兒使用,將它做爲物化視圖的表引擎。而這裏的物化視圖是做爲其餘數據表上層的一種查詢視圖。
AggregatingMergeTree一般做爲物化視圖的表引擎,與普通MergeTree搭配使用。
-- 建立一個MereTree引擎的明細表 -- 用於存儲全量的明細數據 -- 對外提供實時查詢 CREATE TABLE emp_mergetree_base ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資' )ENGINE=MergeTree() ORDER BY (emp_id,name) PARTITION BY work_place ; -- 建立一張物化視圖 -- 使用AggregatingMergeTree表引擎 CREATE MATERIALIZED VIEW view_emp_agg ENGINE = AggregatingMergeTree() PARTITION BY emp_id ORDER BY (emp_id,name) AS SELECT emp_id, name, sumState(salary) AS salary FROM emp_mergetree_base GROUP BY emp_id,name; -- 向基礎明細表emp_mergetree_base插入數據 INSERT INTO emp_mergetree_base VALUES (1,'tom','上海',25,'技術部',20000), (1,'tom','上海',26,'人事部',10000); -- 查詢物化視圖 SELECT emp_id, name , sumMerge(salary) FROM view_emp_agg GROUP BY emp_id,name; -- 結果 ┌─emp_id─┬─name─┬─sumMerge(salary)─┐ │ 1 │ tom │ 30000.00 │ └────────┴──────┴──────────────────┘
CollapsingMergeTree就是一種經過以增代刪的思路,支持行級數據修改和刪除的表引擎。它經過定義一個sign標記位字段,記錄數據行的狀態。若是sign標記爲1,則表示這是一行有效的數據;若是sign標記爲-1,則表示這行數據須要被刪除。當CollapsingMergeTree分區合併時,同一數據分區內,sign標記爲1和-1的一組數據會被抵消刪除。
每次須要新增數據時,寫入一行sign標記爲1的數據;須要刪除數據時,則寫入一行sign標記爲-1的數據。
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] ( name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1], name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2], ... ) ENGINE = CollapsingMergeTree(sign) [PARTITION BY expr] [ORDER BY expr] [SAMPLE BY expr] [SETTINGS name=value, ...]
上面的建表語句使用CollapsingMergeTree(sign),其中字段sign是一個Int8類型的字段
CREATE TABLE emp_collapsingmergetree ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資', sign Int8 )ENGINE=CollapsingMergeTree(sign) ORDER BY (emp_id,name) PARTITION BY work_place ;
CollapsingMergeTree一樣是以ORDER BY排序鍵做爲判斷數據惟一性的依據。
-- 插入新增數據,sign=1表示正常數據 INSERT INTO emp_collapsingmergetree VALUES (1,'tom','上海',25,'技術部',20000,1); -- 更新上述的數據 -- 首先插入一條與原來相同的數據(ORDER BY字段一致),並將sign置爲-1 INSERT INTO emp_collapsingmergetree VALUES (1,'tom','上海',25,'技術部',20000,-1); -- 再插入更新以後的數據 INSERT INTO emp_collapsingmergetree VALUES (1,'tom','上海',25,'技術部',30000,1); -- 查看一下結果 cdh04 :) select * from emp_collapsingmergetree ; SELECT * FROM emp_collapsingmergetree ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 30000.00 │ 1 │ └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ -1 │ └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ 1 │ └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘ -- 執行分區合併操做 optimize table emp_collapsingmergetree; -- 再次查詢,sign=1與sign=-1的數據相互抵消了,即被刪除 cdh04 :) select * from emp_collapsingmergetree ; SELECT * FROM emp_collapsingmergetree ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 30000.00 │ 1 │ └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘
分數數據摺疊不是實時的,須要後臺進行Compaction操做,用戶也可使用手動合併命令,可是效率會很低,通常不推薦在生產環境中使用。
當進行彙總數據操做時,能夠經過改變查詢方式,來過濾掉被刪除的數據
SELECT emp_id, name, sum(salary * sign) FROM emp_collapsingmergetree GROUP BY emp_id, name HAVING sum(sign) > 0
只有相同分區內的數據纔有可能被摺疊。其實,當咱們修改或刪除數據時,這些被修改的數據一般是在一個分區內的,因此不會產生影響。
值得注意的是:CollapsingMergeTree對於寫入數據的順序有着嚴格要求,不然致使沒法正常摺疊。
-- 建表 CREATE TABLE emp_collapsingmergetree_order ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資', sign Int8 )ENGINE=CollapsingMergeTree(sign) ORDER BY (emp_id,name) PARTITION BY work_place ; -- 先插入須要被刪除的數據,即sign=-1的數據 INSERT INTO emp_collapsingmergetree_order VALUES (1,'tom','上海',25,'技術部',20000,-1); -- 再插入sign=1的數據 INSERT INTO emp_collapsingmergetree_order VALUES (1,'tom','上海',25,'技術部',20000,1); -- 查詢表 SELECT * FROM emp_collapsingmergetree_order ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ 1 │ └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ -1 │ └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘ -- 執行合併操做 optimize table emp_collapsingmergetree_order; -- 再次查詢表 -- 舊數據依然存在 SELECT * FROM emp_collapsingmergetree_order; ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ -1 │ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ 1 │ └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┘
若是數據的寫入程序是單線程執行的,則可以較好地控制寫入順序;若是須要處理的數據量很大,數據的寫入程序一般是多線程執行的,那麼此時就不能保障數據的寫入順序了。在這種狀況下,CollapsingMergeTree的工做機制就會出現問題。可是能夠經過VersionedCollapsingMergeTree的表引擎獲得解決。
上面提到CollapsingMergeTree表引擎對於數據寫入亂序的狀況下,不可以實現數據摺疊的效果。VersionedCollapsingMergeTree表引擎的做用與CollapsingMergeTree徹底相同,它們的不一樣之處在於,VersionedCollapsingMergeTree對數據的寫入順序沒有要求,在同一個分區內,任意順序的數據都可以完成摺疊操做。
VersionedCollapsingMergeTree使用version列來實現亂序狀況下的數據摺疊。
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] ( name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1], name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2], ... ) ENGINE = VersionedCollapsingMergeTree(sign, version) [PARTITION BY expr] [ORDER BY expr] [SAMPLE BY expr] [SETTINGS name=value, ...]
能夠看出:該引擎除了須要指定一個sign標識以外,還須要指定一個UInt8類型的version版本號。
CREATE TABLE emp_versioned ( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資', sign Int8, version Int8 )ENGINE=VersionedCollapsingMergeTree(sign, version) ORDER BY (emp_id,name) PARTITION BY work_place ; -- 先插入須要被刪除的數據,即sign=-1的數據 INSERT INTO emp_versioned VALUES (1,'tom','上海',25,'技術部',20000,-1,1); -- 再插入sign=1的數據 INSERT INTO emp_versioned VALUES (1,'tom','上海',25,'技術部',20000,1,1); -- 在插入一個新版本數據 INSERT INTO emp_versioned VALUES (1,'tom','上海',25,'技術部',30000,1,2); -- 先不執行合併,查看錶數據 cdh04 :) select * from emp_versioned; SELECT * FROM emp_versioned ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┬─version─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 30000.00 │ 1 │ 2 │ └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┴─────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┬─version─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ 1 │ 1 │ └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┴─────────┘ ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┬─version─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ -1 │ 1 │ └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┴─────────┘ -- 獲取正確查詢結果 SELECT emp_id, name, sum(salary * sign) FROM emp_versioned GROUP BY emp_id, name HAVING sum(sign) > 0; -- 手動合併 optimize table emp_versioned; -- 再次查詢 cdh04 :) select * from emp_versioned; SELECT * FROM emp_versioned ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┬─sign─┬─version─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 30000.00 │ 1 │ 2 │ └────────┴──────┴────────────┴─────┴────────┴──────────┴──────┴─────────┘
可見上面雖然在插入數據亂序的狀況下,依然可以實現摺疊的效果。之因此可以達到這種效果,是由於在定義version字段以後,VersionedCollapsingMergeTree會自動將version做爲排序條件並增長到ORDER BY的末端,就上述的例子而言,最終的排序字段爲ORDER BY emp_id,name,version desc。
該引擎用來對 Graphite數據進行'瘦身'及彙總。對於想使用CH來存儲Graphite數據的開發者來講可能有用。
若是不須要對Graphite數據作彙總,那麼可使用任意的CH表引擎;但若須要,那就採用 GraphiteMergeTree 引擎。它能減小存儲空間,同時能提升Graphite數據的查詢效率。
ClickHouse提供了許多與外部系統集成的方法,包括一些表引擎。這些表引擎與其餘類型的表引擎相似,能夠用於將外部數據導入到ClickHouse中,或者在ClickHouse中直接操做外部數據源。
例如直接讀取HDFS的文件或者MySQL數據庫的表。這些表引擎只負責元數據管理和數據查詢,而它們自身一般並不負責數據的寫入,數據文件直接由外部系統提供。目前ClickHouse提供了下面的外部集成表引擎:
ENGINE = HDFS(URI, format)
-- 建表 CREATE TABLE hdfs_engine_table( emp_id UInt16 COMMENT '員工id', name String COMMENT '員工姓名', work_place String COMMENT '工做地點', age UInt8 COMMENT '員工年齡', depart String COMMENT '部門', salary Decimal32(2) COMMENT '工資' ) ENGINE=HDFS('hdfs://cdh03:8020/user/hive/hdfs_engine_table', 'CSV'); -- 寫入數據 INSERT INTO hdfs_engine_table VALUES (1,'tom','上海',25,'技術部',20000),(2,'jack','上海',26,'人事部',10000); -- 查詢數據 cdh04 :) select * from hdfs_engine_table; SELECT * FROM hdfs_engine_table ┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ └────────┴──────┴────────────┴─────┴────────┴──────────┘ --再在HDFS上其對應的文件,添加幾條數據,再次查看 cdh04 :) select * from hdfs_engine_table; SELECT * FROM hdfs_engine_table ┌─emp_id─┬─name───┬─work_place─┬─age─┬─depart─┬───salary─┐ │ 1 │ tom │ 上海 │ 25 │ 技術部 │ 20000.00 │ │ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │ │ 3 │ lili │ 北京 │ 28 │ 技術部 │ 20000.00 │ │ 4 │ jasper │ 杭州 │ 27 │ 人事部 │ 8000.00 │ └────────┴────────┴────────────┴─────┴────────┴──────────┘
能夠看出,這種方式與使用Hive相似,咱們直接能夠將HDFS對應的文件映射成ClickHouse中的一張表,這樣就可使用SQL操做HDFS上的文件了。
值得注意的是:ClickHouse並不可以刪除HDFS上的數據,當咱們在ClickHouse客戶端中刪除了對應的表,只是刪除了表結構,HDFS上的文件並無被刪除,這一點跟Hive的外部表十分類似。
在上一篇文章[篇一|ClickHouse快速入門]中介紹了MySQL數據庫引擎,即ClickHouse能夠建立一個MySQL數據引擎,這樣就能夠在ClickHouse中操做其對應的數據庫中的數據。其實,ClickHouse一樣支持MySQL表引擎,即映射一張MySQL中的表到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], ... ) ENGINE = MySQL('host:port', 'database', 'table', 'user', 'password'[, replace_query, 'on_duplicate_clause']);
-- 鏈接MySQL中clickhouse數據庫的test表 CREATE TABLE mysql_engine_table( id Int32, name String ) ENGINE = MySQL( '192.168.200.241:3306', 'clickhouse', 'test', 'root', '123qwe'); -- 查詢數據 cdh04 :) SELECT * FROM mysql_engine_table; SELECT * FROM mysql_engine_table ┌─id─┬─name──┐ │ 1 │ tom │ │ 2 │ jack │ │ 3 │ lihua │ └────┴───────┘ -- 插入數據,會將數據插入MySQL對應的表中 -- 因此當查詢MySQL數據時,會發現新增了一條數據 INSERT INTO mysql_engine_table VALUES(4,'robin'); -- 再次查詢 cdh04 :) select * from mysql_engine_table; SELECT * FROM mysql_engine_table ┌─id─┬─name──┐ │ 1 │ tom │ │ 2 │ jack │ │ 3 │ lihua │ │ 4 │ robin │ └────┴───────┘
注意:對於MySQL表引擎,不支持UPDATE和DELETE操做,好比執行下面命令時,會報錯:
-- 執行更新 ALTER TABLE mysql_engine_table UPDATE name = 'hanmeimei' WHERE id = 1; -- 執行刪除 ALTER TABLE mysql_engine_table DELETE WHERE id = 1; -- 報錯 DB::Exception: Mutations are not supported by storage MySQL.
JDBC表引擎不只能夠對接MySQL數據庫,還可以與PostgreSQL等數據庫。爲了實現JDBC鏈接,ClickHouse使用了clickhouse-jdbc-bridge的查詢代理服務。
首先咱們須要下載clickhouse-jdbc-bridge,而後按照ClickHouse的github中的步驟進行編譯,編譯完成以後會有一個clickhouse-jdbc-bridge-1.0.jar的jar文件,除了須要該文件以外,還須要JDBC的驅動文件,本文使用的是MySQL,因此還須要下載MySQL驅動包。將MySQL的驅動包和clickhouse-jdbc-bridge-1.0.jar文件放在了/opt/softwares路徑下,執行以下命令:
[root@cdh04 softwares]# java -jar clickhouse-jdbc-bridge-1.0.jar --driver-path . --listen-host cdh04
其中--driver-path
是MySQL驅動的jar所在的路徑,listen-host
是代理服務綁定的主機。默認狀況下,綁定的端口是:9019。上述jar包的下載:
連接: https://pan.baidu.com/s/1ZcvF...
提取碼:la9n
而後咱們再配置/etc/clickhouse-server/config.xml
,在文件中添加以下配置,而後重啓服務。
<jdbc_bridge> <host>cdh04</host> <port>9019</port> </jdbc_bridge>
SELECT * FROM jdbc( 'jdbc:mysql://192.168.200.241:3306/?user=root&password=123qwe', 'clickhouse', 'test');
-- 語法 CREATE TABLE [IF NOT EXISTS] [db.]table_name ( columns list... ) ENGINE = JDBC(dbms_uri, external_database, external_table) -- MySQL建表 CREATE TABLE jdbc_table_mysql ( order_id INT NOT NULL AUTO_INCREMENT, amount FLOAT NOT NULL, PRIMARY KEY (order_id)); INSERT INTO jdbc_table_mysql VALUES (1,200); -- 在ClickHouse中建表 CREATE TABLE jdbc_table ( order_id Int32, amount Float32 ) ENGINE JDBC( 'jdbc:mysql://192.168.200.241:3306/?user=root&password=123qwe', 'clickhouse', 'jdbc_table_mysql'); -- 查詢數據 cdh04 :) select * from jdbc_table; SELECT * FROM jdbc_table ┌─order_id─┬─amount─┐ │ 1 │ 200 │ └──────────┴────────┘
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] ( name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1], name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2], ... ) ENGINE = Kafka() SETTINGS kafka_broker_list = 'host:port', kafka_topic_list = 'topic1,topic2,...', kafka_group_name = 'group_name', kafka_format = 'data_format'[,] [kafka_row_delimiter = 'delimiter_symbol',] [kafka_schema = '',] [kafka_num_consumers = N,] [kafka_max_block_size = 0,] [kafka_skip_broken_messages = N,] [kafka_commit_every_batch = 0,] [kafka_thread_per_consumer = 0]
kafka_broker_list
:逗號分隔的brokers地址 (localhost:9092).kafka_topic_list
:Kafka 主題列表,多個主題用逗號分隔.kafka_group_name
:消費者組.kafka_format
– Message format. 好比JSONEachRow
、JSON、CSV等等在kafka中建立ck_topic主題,並向該主題寫入數據
CREATE TABLE kafka_table ( id UInt64, name String ) ENGINE = Kafka() SETTINGS kafka_broker_list = 'cdh04:9092', kafka_topic_list = 'ck_topic', kafka_group_name = 'group1', kafka_format = 'JSONEachRow' ; -- 查詢 cdh04 :) select * from kafka_table ; SELECT * FROM kafka_table ┌─id─┬─name─┐ │ 1 │ tom │ └────┴──────┘ ┌─id─┬─name─┐ │ 2 │ jack │ └────┴──────┘
當咱們一旦查詢完畢以後,ClickHouse會刪除表內的數據,其實Kafka表引擎只是一個數據管道,咱們能夠經過物化視圖的方式訪問Kafka中的數據。
-- 建立Kafka引擎表 CREATE TABLE kafka_table_consumer ( id UInt64, name String ) ENGINE = Kafka() SETTINGS kafka_broker_list = 'cdh04:9092', kafka_topic_list = 'ck_topic', kafka_group_name = 'group1', kafka_format = 'JSONEachRow' ; -- 建立一張終端用戶使用的表 CREATE TABLE kafka_table_mergetree ( id UInt64 , name String )ENGINE=MergeTree() ORDER BY id ; -- 建立物化視圖,同步數據 CREATE MATERIALIZED VIEW consumer TO kafka_table_mergetree AS SELECT id,name FROM kafka_table_consumer ; -- 查詢,屢次查詢,已經被查詢的數據依然會被輸出 cdh04 :) select * from kafka_table_mergetree; SELECT * FROM kafka_table_mergetree ┌─id─┬─name─┐ │ 2 │ jack │ └────┴──────┘ ┌─id─┬─name─┐ │ 1 │ tom │ └────┴──────┘
Memory表引擎直接將數據保存在內存中,數據既不會被壓縮也不會被格式轉換。當ClickHouse服務重啓的時候,Memory表內的數據會所有丟失。通常在測試時使用。
CREATE TABLE table_memory ( id UInt64, name String ) ENGINE = Memory();
Distributed表引擎是分佈式表的代名詞,它自身不存儲任何數據,數據都分散存儲在某一個分片上,可以自動路由數據至集羣中的各個節點,因此Distributed表引擎須要和其餘數據表引擎一塊兒協同工做。
因此,一張分佈式表底層會對應多個本地分片數據表,由具體的分片表存儲數據,分佈式表與分片表是一對多的關係
Distributed表引擎的定義形式以下所示
Distributed(cluster_name, database_name, table_name[, sharding_key])
各個參數的含義分別以下:
尖叫提示:建立分佈式表是讀時檢查的機制,也就是說對建立分佈式表和本地表的順序並無強制要求。
一樣值得注意的是,在上面的語句中使用了ON CLUSTER分佈式DDL,這意味着在集羣的每一個分片節點上,都會建立一張Distributed表,這樣即可以從其中任意一端發起對全部分片的讀、寫請求。
-- 建立一張分佈式表 CREATE TABLE IF NOT EXISTS user_cluster ON CLUSTER cluster_3shards_1replicas ( id Int32, name String )ENGINE = Distributed(cluster_3shards_1replicas, default, user_local,id);
建立完成上面的分佈式表時,在每臺機器上查看錶,發現每臺機器上都存在一張剛剛建立好的表。
接下來就須要建立本地表了,在每臺機器上分別建立一張本地表:
CREATE TABLE IF NOT EXISTS user_local ( id Int32, name String )ENGINE = MergeTree() ORDER BY id PARTITION BY id PRIMARY KEY id;
咱們先在一臺機器上,對user_local表進行插入數據,而後再查詢user_cluster表
-- 插入數據 cdh04 :) INSERT INTO user_local VALUES(1,'tom'),(2,'jack'); -- 查詢user_cluster表,可見經過user_cluster表能夠操做全部的user_local表 cdh04 :) select * from user_cluster; ┌─id─┬─name─┐ │ 2 │ jack │ └────┴──────┘ ┌─id─┬─name─┐ │ 1 │ tom │ └────┴──────┘
接下來,咱們再向user_cluster中插入一些數據,觀察user_local表數據變化,能夠發現數據被分散存儲到了其餘節點上了。
-- 向user_cluster插入數據 cdh04 :) INSERT INTO user_cluster VALUES(3,'lilei'),(4,'lihua'); -- 查看user_cluster數據 cdh04 :) select * from user_cluster; ┌─id─┬─name─┐ │ 2 │ jack │ └────┴──────┘ ┌─id─┬─name──┐ │ 3 │ lilei │ └────┴───────┘ ┌─id─┬─name─┐ │ 1 │ tom │ └────┴──────┘ ┌─id─┬─name──┐ │ 4 │ lihua │ └────┴───────┘ -- 在cdh04上查看user_local cdh04 :) select * from user_local; ┌─id─┬─name─┐ │ 2 │ jack │ └────┴──────┘ ┌─id─┬─name──┐ │ 3 │ lilei │ └────┴───────┘ ┌─id─┬─name─┐ │ 1 │ tom │ └────┴──────┘ -- 在cdh05上查看user_local cdh05 :) select * from user_local; ┌─id─┬─name──┐ │ 4 │ lihua │ └────┴───────┘
ClickHouse提供了很是多的表引擎,每一種表引擎都有各自的適用場景。經過特定的表引擎支撐特定的場景,十分靈活。本文主要分享了ClickHouse提供的常見表引擎,並對每種表引擎給出了適用場景和使用示例,但願對你有所幫助。
公衆號『大數據技術與數倉』,回覆『資料』領取大數據資料包