1. 程式人生 > 實用技巧 >實時電商數倉(十九)之實時資料儲存與查詢(九) Clickhouse (四) 表引擎

實時電商數倉(十九)之實時資料儲存與查詢(九) Clickhouse (四) 表引擎

1 表引擎的使用

表引擎是clickhouse的一大特色。可以說, 表引擎決定了如何儲存標的資料。包括:

1)資料的儲存方式和位置,寫到哪裡以及從哪裡讀取資料

2)支援哪些查詢以及如何支援。

3)併發資料訪問。

4)索引的使用(如果存在)。

5)是否可以執行多執行緒請求。

6)資料複製引數。

表引擎的使用方式就是必須顯形在建立表時定義該表使用的引擎,以及引擎使用的相關引數。如:

create table t_tinylog ( id String, name String) engine=TinyLog;

特別注意:引擎的名稱大小寫敏感

2 TinyLog

以列檔案的形式儲存在磁碟上,不支援索引,沒有併發控制。一般儲存少量資料的小表,生產環境上作用有限。可以用於平時練習測試用。

3 Memory

記憶體引擎,資料以未壓縮的原始形式直接儲存在記憶體當中,伺服器重啟資料就會消失。讀寫操作不會相互阻塞,不支援索引。簡單查詢下有非常非常高的效能表現(超過10G/s)。

一般用到它的地方不多,除了用來測試,就是在需要非常高的效能,同時資料量又不太大(上限大概 1 億行)的場景。

4 MergeTree

Clickhouse 中最強大的表引擎當屬 MergeTree (合併樹)引擎及該系列(*MergeTree)中的其他引擎。地位可以相當於innodb之於Mysql 而且基於MergeTree,還衍生除了很多小弟,也是非常有特色的引擎。

建表語句

create table t_order_mt(
    id UInt32,
    sku_id String,
    total_amount Decimal(
16,2), create_time Datetime ) engine =MergeTree partition by toYYYYMMDD(create_time) primary key (id) order by (id,sku_id) insert into t_order_mt values(101,'sku_001',1000.00,'2020-06-01 12:00:00') , (102,'sku_002',2000.00,'2020-06-01 11:00:00'), (102,'sku_004',2500.00,'2020-06-01 12:00:00'), (102,'sku_002',2000.00,'2020-06-01 13:00:00') (
102,'sku_002',12000.00,'2020-06-01 13:00:00') (102,'sku_002',600.00,'2020-06-02 12:00:00')

MergeTree其實還有很多引數(絕大多數用預設值即可),但是三個引數是更加重要的,也涉及了關於MergeTree的很多概念。

4.1 partition by 分割槽 (可選項)

作用:學過hive的應該都不陌生,分割槽的目的主要是降低掃描的範圍,優化查詢速度。

如果不填:只會使用一個分割槽。

分割槽目錄:MergeTree 是以列檔案+索引檔案+表定義檔案組成的,但是如果設定了分割槽那麼這些檔案就會儲存到不同的分割槽目錄中。

並行:分割槽後,面對涉及跨分割槽的查詢統計,clickhouse會以分割槽為單位並行處理。

資料寫入與分割槽合併:

任何一個批次的資料寫入都會產生一個臨時分割槽,不會納入任何一個已有的分割槽。寫入後的某個時刻(大概10-15分鐘後),clickhouse會自動執行合併操作(等不及也可以手動通過optimize執行),把臨時分割槽的資料,合併到已有分割槽中。

optimize table xxxx [final]

4.2 primary key主鍵(可選)

clickhouse中的主鍵,和其他資料庫不太一樣,它只提供了資料的一級索引,但是卻不是唯一約束。這就意味著是可以存在相同primary key的資料的。

主鍵的設定主要依據是查詢語句中的where 條件。

根據條件通過對主鍵進行某種形式的二分查詢,能夠定位到對應的index granularity,避免了全包掃描。

index granularity 直接翻譯的話就是索引粒度,指在稀疏索引中兩個相鄰索引對應資料的間隔。clickhouse中的MergeTree預設是8192。官方不建議修改這個值,除非該列存在大量重複值,比如在一個分割槽中幾萬行才有一個不同資料。

稀疏索引:

稀疏索引的好處就是可以用很少的索引資料,定位更多的資料,代價就是隻能定位到索引粒度的第一行,然後再進行進行一點掃描。

4.3 order by (必選)

order by 設定了分割槽內的資料按照哪些欄位順序進行有序儲存。

order by是MergeTree中唯一一個必填項,甚至比primary key 還重要,因為當用戶不設定主鍵的情況,很多處理會依照order by的欄位進行處理(比如後面會講的去重和彙總)。

要求:主鍵必須是order by欄位的字首欄位。

比如order by 欄位是 (id,sku_id) 那麼主鍵必須是id 或者(id,sku_id)

4.4 二級索引

目前在clickhouse的官網上二級索引的功能是被標註為實驗性的。

所以使用二級索引前需要增加設定

set allow_experimental_data_skipping_indices=1;
    create table t_order_mt2(
    id UInt32,
    sku_id String,
    total_amount Decimal(16,2),
    create_time  Datetime,
    INDEX a total_amount TYPE minmax GRANULARITY 5
 ) engine =MergeTree
 partition by toYYYYMMDD(create_time)
   primary key (id)
   order by (id, sku_id)

其中GRANULARITY N 是設定二級索引對於一級索引粒度的粒度。

那麼在使用下面語句進行測試,可以看出二級索引能夠為非主鍵欄位的查詢發揮作用。

[bigdata@hdp1 t_order_mt]$ clickhouse-client  --send_logs_level=trace <<< 'select * from test1.t_order_mt  where total_amount > toDecimal32(900., 2)'

4.5 資料TTL

TTLTime To LiveMergeTree提供了可以管理資料或者列的生命週期的功能。

列級別TTL

  create table t_order_mt3(
    id UInt32,
    sku_id String,
    total_amount Decimal(16,2)  TTL create_time+interval 10 SECOND,
    create_time  Datetime 
 ) engine =MergeTree
 partition by toYYYYMMDD(create_time)
   primary key (id)
   order by (id, sku_id)

插入資料

  insert into  t_order_mt3
values(106,'sku_001',1000.00,'2020-06-12 22:52:30') ,
(107,'sku_002',2000.00,'2020-06-12 22:52:30'),
(110,'sku_003',600.00,'2020-06-13 12:00:00')

表級TTL

針對整張表

下面的這條語句是資料會在create_time 之後10秒丟失

alter table t_order_mt3 MODIFY TTL create_time + INTERVAL 10 SECOND;

涉及判斷的欄位必須是Date或者Datetime型別,推薦使用分割槽的日期欄位。

能夠使用的時間週期:

- SECOND

- MINUTE

- HOUR

- DAY

- WEEK

- MONTH

- QUARTER

- YEAR

5 ReplacingMergeTree

ReplacingMergeTreeMergeTree的一個變種,它儲存特性完全繼承MergeTree,只是多了一個去重的功能。

儘管MergeTree可以設定主鍵,但是primary key其實沒有唯一約束的功能。如果你想處理掉重複的資料,可以藉助這個ReplacingMergeTree。

去重時機:資料的去重只會在合併的過程中出現。合併會在未知的時間在後臺進行,所以你無法預先作出計劃。有一些資料可能仍未被處理。

去重範圍:如果表經過了分割槽,去重只會在分割槽內部進行去重,不能執行跨分割槽的去重。

所以ReplacingMergeTree能力有限, ReplacingMergeTree 適用於在後臺清除重複的資料以節省空間,但是它不保證沒有重複的資料出現。

  create table t_order_rmt(
    id UInt32,
    sku_id String,
    total_amount Decimal(16,2) ,
    create_time  Datetime 
 ) engine =ReplacingMergeTree(create_time)
 partition by toYYYYMMDD(create_time)
   primary key (id)
   order by (id, sku_id)

ReplacingMergeTree() 填入的引數為版本欄位,重複資料保留版本欄位值最大的。

如果不填版本欄位,預設保留最後一條。

insert into  t_order_rmt
values(101,'sku_001',1000.00,'2020-06-01 12:00:00') ,
(102,'sku_002',2000.00,'2020-06-01 11:00:00'),
(102,'sku_004',2500.00,'2020-06-01 12:00:00'),
(102,'sku_002',2000.00,'2020-06-01 13:00:00')
(102,'sku_002',12000.00,'2020-06-01 13:00:00')
(102,'sku_002',600.00,'2020-06-02 12:00:00')
SELECT *
FROM t_order_rmt

OPTIMIZE TABLE t_order_rmt FINAL

SELECT *FROM t_order_rmt

通過測試得到結論:

  • 實際上是使用order by 欄位作為唯一鍵。
  • 去重不能跨分割槽。
  • 只有合併分割槽才會進行去重。
  • 認定重複的資料保留,版本欄位值最大的。
  • 如果版本欄位相同則保留最後一筆。

6 SummingMergeTree

對於不查詢明細,只關心以維度進行彙總聚合結果的場景。如果只使用普通的MergeTree的話,無論是儲存空間的開銷,還是查詢時臨時聚合的開銷都比較大。

Clickhouse 為了這種場景,提供了一種能夠“預聚合”的引擎,SummingMergeTree.

表定義

create table t_order_smt(
    id UInt32,
    sku_id String,
    total_amount Decimal(16,2) ,
    create_time  Datetime 
 ) engine =SummingMergeTree(total_amount)
 partition by toYYYYMMDD(create_time)
   primary key (id)
   order by (id,sku_id )

插入資料

insert into  t_order_smt
values(101,'sku_001',1000.00,'2020-06-01 12:00:00') ,
(102,'sku_002',2000.00,'2020-06-01 11:00:00'),
(102,'sku_004',2500.00,'2020-06-01 12:00:00'),
(102,'sku_002',2000.00,'2020-06-01 13:00:00')
(102,'sku_002',12000.00,'2020-06-01 13:00:00')
(102,'sku_002',600.00,'2020-06-02 12:00:00')

optimize table t_order_smt final;

通過結果可以得到以下結論:

SummingMergeTree()中指定的列作為彙總資料列。可以填寫多列必須數字列,如果不填,以所有非維度列且為數字列的欄位為彙總資料列。

order by 的列為準,作為維度列。

其他的列保留第一行。

不在一個分割槽的資料不會被聚合。

設計聚合表的話,唯一鍵值、流水號可以去掉,所有欄位全部是維度、度量或者時間戳。

能不能直接 select total_amount from province_name=’’ and create_date=’xxx’ 來得到彙總值?

不行,可能會包含一些還沒來得及聚合的臨時明細

select sum(total_amount) from province_name=’’ and create_date=’xxx’