clickHouse 基本概念與用法


 

官方文檔鏈接 : 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 優點

  1. 靈活的MPP架構,支持線性擴展,簡單方便,高可靠

  2. 多服務器分布式處理數據,完備的DBMS系統

  3. 底層數據列式存儲,支持壓縮,優化數據存儲,優化索引數據

  4. 容錯率高,跑分快。比 Vertica 快 5 倍,比Hive 快 279倍,比Mysql 快800 倍,其可處理的數據級別可達到 10 億級別

  5. 功能多:支持數據統計分析各種場景,支持類SQL查詢,異地復制部署,海量數據存儲,分布式運算,快速閃電的性能,實時數據分析,出色的函數支持

1.3 缺點

  1. 不支持事務,不支持真正的刪除/更新

  2. 單節點不支持高並發,一台機器官方建議qps為100 ,但可以通過修改配置文件增加連接數

  3. 不支持二級索引

  4. 雖然支持多表join查詢,但不擅長多表join查詢 。 因此在創建表結構的時候,優先考慮創建一張較大較寬的表,將所有的數據放在一張表中

  5. 元數據管理需要人為干預

  6. 盡量做到1000條以上的批量數據寫入,避免逐行的insert 或者小批量的insert ,update ,delete 操作,因為寫入一條記錄和1000條記錄所需要的資源和時間相差無幾。

1.4. 應用場景

  1. 查詢業務遠遠超過增刪改的業務。

  2. 要求實時返回查詢結果

  3. 數據需要以大批次(大於1000行)更新,而不是單行更新,或者根本沒有更新操作

  4. 讀取數據時,會從數據庫中提取出大量的行,但只用到一小部分

  5. 表很寬,即表中含有大量的字段,列。每次查詢中只會查詢一個大寬表,除了一個大寬表,其余都是小表

  6. 列的值是比較小的數值和短字符串,而不是較長的文本

  7. 查詢頻率相對較低,通常每台服務器每秒查詢數百次或更少

  8. 對於簡單查詢,允許大於50ms的延遲

  9. 在處理單個查詢時需要提高吞吐量,每台服務器每秒高達數十億行

  10. 不需要事務

  11. 數據一致性要求較低( 原子性 持久性 一致性 隔離性 )

  12. 查詢結果顯著小於數據源,即數據有過濾或者聚合,但查詢返回的結果不超過單個服務器內存的大小

2. 核心概念

2.1 數據分片

數據分片 是將數據進行橫向切分,這是一種在面對海量數據的場景下,解決存儲和查詢瓶頸的有效手段,是一種分治思想的體現,Clickhouse支持分片而分片則依賴集群,每個集群由1到多個分片組成,每個分片對應了Clickhouse的一個服務節點。分片的數量上限取決於節點數量,一個分片只能對應1個服務節點。Clickhouse並不向其他分布式系統那樣,擁有高度自動化的分片功能。Clickhouse的提供了本地表和分布式表的概念,一張本地表等同於一份數據的分片,而分布式表本身並不存儲任何數據,它是本地表的訪問代理,其作用類似分庫中間件。借助分布式表,能夠代理訪問多個數據分片,從而實現分布式查詢,這種設計類似數據庫的分庫和分表,十分靈活。例如在業務系統上線的初期,數據體量並不高,此時數據並不需要多個分片,所以使用單個節點的本地表(單個數據分片)即可滿足業務需求,待到業務增長,數據量增大的時候,再通過新增數據分片的方式分流數據,並通過分布式表實現分布式查詢。

2.2 列式存儲

列式存儲是相對於傳統關系型數據庫的行式存儲來說的。簡單來說兩者的區別就是如何組織表

img

從上圖可以很清楚地看到,行式存儲下一張表的數據都是放在一起的,但列式存儲下都被分開保存了。將不同的行相同的列存儲再一起,這些數據具有相同的結構和長度,重復率更高,可以壓縮比例更高,降低IO次數,提高查詢效率。

  1. 在實際的應用場景中,往往需要讀大量行,但是少數幾個列,在行存儲模式下,數據按行連續存儲,所有列的數據都存儲再一個bloCK中,不參與計算的列在IO時也要全部讀出,讀取操作被嚴重放大,在列存儲模式下,值需要讀取參與計算的列即可,極大的降低的IO cast ,加快了查詢效率。

  2. 更高的壓縮比意味着更小的data size ,從磁盤中讀取相應數據耗時更短。

  3. 自由的壓縮算法選擇,不同列的數據具有不同的數據類型,適用的壓縮算法也就不盡相同,可以針對不同列類型,選擇最合適的壓縮算法

  4. 高壓縮比,意味着同樣大小的內存能夠存放更多的數據,系統cache 效果更好

    官方顯示,通過適用列式存儲,在某些分析場景中,能后獲得100倍甚至更高的加速效應。

2.3 分區

Clickhouse 支持 PARTITION BY 子句,在建表的時候可以指定按照任意合法表達式進行數據分區操作,比如通過toYYYYMM() 將數據按月進行分區,toMonday() 將數據按照星期進行分區,對Enum 類型的列直接每種取值作為一個分區等,數據以分區的形式統一管理和維護一批數據。

2.4 副本

數據存儲副本,在集群模式下可實現高可用,在CK中通過復制集,實現了數據可靠性,也通過多副本的方式,增加了CK查詢的並發能力。

一般有2種方式:(1)基於ZooKeeper的表復制方式,(2)基於Cluster的復制方式

由於推薦的數據寫入方式為本地表寫入,禁止分布式表寫入,因此復制表主要考慮ZooKeeper的表復制方案。

3. 數據類型

3.1 數字類型

  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]

  1. FloatX

  • Float32float.

  • Float64double.

  1. 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) )

  2. bool

    0 或者 1

3.2 字符串類型

  1. String

  2. FixedString

  3. UUID

3.3 時間類型

  1. Date

  2. DateTime

  3. DateTime64

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秒后刪除。由下面這個參數控制: image-20210818142930907

我們可以使用以下辦法來解決這個問題:

  1. 使用普通數據庫而不是原子數據庫。 create database … Engine=Ordinary.

  2. 使用uniq ZK路徑。{uuid}/clickhouse/tables/{layer}-{shard}-{uuid}/

  3. 減少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 );


將查詢的結果全部插入一張表  
INSERT  into wenl_tmp_fact_general_report_production
select data_time,SUM(active_power) sum_of_Active_power
from wenl_dm_fact_daily_report_10m_detail_tmp
group by data_time ;

插入之前數據類型轉換,默認接受的是字符串類型,插入之前需要做一下數據類型轉換
INSERT into wenl_meter_production_day values (5,'2021-01-01',toFloat64('1.0'),toFloat64('1.1'),toFloat64('1.2'),toFloat64('1.3'),toFloat64('1.4'),toFloat64('1.5'));

4.4 刪除數據

按照分區刪除數據
ALTER TABLE db_name.table_name  DROP PARTITIION "20200601";
按照條件刪除
ALTER TABLE db_name.table_name  DELETE WHERE day='20210818'

清空數據表中的數據
TRUNCATE TABLE 表名 + ON CLUNE 集群名;
清空一張表的數據必須要加集群的名字,否則執行不成功

4.5 修改數據

ALTER TABLE <table_name> UPDATE col1 = expr1, ... WHERE <filter>
alter table table_name update name = '華為',phone = '123456' where id = 1;
ALTER table wenl_dim_wtg_full update pc1_id = '0'  where wtg_id ='03glgjyZ';
ALTER table wenl_dim_wtg_full update production_scale = '000' ,production_slope = '0000' where wtg_id ='00Ood1S3';
ALTER TABLE wenl1.wenl_tmp_fact_general_report_date  DELETE WHERE data_time = '2010-01-04';

4.6 查詢數據

SELECT wtg_id ,production_scale ,production_slope  from wenl_dim_wtg_full limit 10 ;

SELECT wtg_id ,wtg_name ,site_id ,site_name , pc1_id  from wenl_dim_wtg_full wdwf  limit 100;

// 分組求和
SELECT  SUM(active_power)  as sum  from wenl_dm_fact_daily_report_10m_detail_tmp group by data_time ;

// 分組求平均數  
SELECT data_time ,AVG(wind_speed) as avg_of_wind_speed   from wenl_dm_fact_daily_report_10m_detail_tmp group by data_time ;

// 聚合分組查詢
SELECT   site_id , data_time, SUM(active_power)
from     wenl_dm_fact_daily_report_10m_detail_tmp wdfdrmdt
group by data_time ,site_id ;
注意:
   group by 后面可以接一個字段名,也可以接兩個或者多個字段,但是注意,前面select 接的字段,只能出現 group by 后面跟着的字段,或者其他字段的聚合項,因為 group by 之后就已經把原來的數據集分組了,顆粒度變大了,就無法查詢到原來下一級別顆粒度的數據。

SELECT   site_id, wtg_id , data_time, SUM(active_power)
from     wenl_dm_fact_daily_report_10m_detail_tmp wdfdrmdt
group by data_time ,site_id ,wtg_id ;

// 左連接查詢
SELECT  
a.wtg_id as wtg_id ,
b.site_id ,
a.data_time as data_time ,
b.sum_of_Active_power as sum_of_Active_power ,
c.avg_of_wind_speed as avg_of_wind_speed
from wenl_tmp_fact_general_report_date a  
left join wenl_tmp_fact_general_report_production b
on ( a.wtg_id = b.wtg_id and a.data_time = b.data_time)
LEFT join
wenl_tmp_fact_general_report_windspeed c
on (a.wtg_id  = c.wtg_id and a.data_time  = c.data_time) ;

4.7 數據類型轉換

SELECT toTypeName(toFloat64('1.0')) aa;

5. MergeTree 系列表引擎

不同的引擎決定了表數據的存儲特點和表數的操作行為:

  1. 決定表存儲再哪里以及以何種方式存儲

  2. 支持哪些查詢以及如何支持

  3. 並發數據訪問,'不同的引擎決定是否支持並發操作

  4. 索引的使用

  5. 是否可以執行多線程請求

  6. 數據復制參數

    表引擎決定了數據在文件系統中的存儲方式,常用的也是官方推薦的存儲引擎是MergeTree系列,如果需要數據副本的話可以使用ReplicateMergeTree系列,相當於MergeTree的副本數據,讀取集群數據需要使用分布式表引擎 Distribute

5.1 MergeTree

該系列的MergeTree引擎和其他引擎 ( *MergeTree) 是最強大的 ClickHouse 表引擎。

MergeTree系列中的引擎旨在將大量數據插入表中。數據被快速逐個寫入表格,然后應用規則在后台合並部分。這種方法比在插入期間不斷重寫存儲中的數據要高效得多。

主要特點:

  • 存儲按主鍵排序的數據。這允許您創建一個小的稀疏索引,幫助更快地查找數據。

  • 如果指定了分區鍵,則可以使用分區

    ClickHouse 支持某些分區操作,這些操作比對具有相同結果的相同數據進行一般操作更有效。ClickHouse 還會自動切斷查詢中指定分區鍵的分區數據。

  • 數據復制支持。ReplicatedMergeTree表族提供數據復制。有關詳細信息,請參閱數據復制

  • 數據采樣支持。如果需要,您可以在表中設置數據采樣方法

5.2 ReplacingMergeTree

該引擎與MergeTree 的不同之處在於它刪除具有相同排序鍵值ORDER BYtable 部分,而不是PRIMARY KEY)的重復條目。

重復數據刪除僅在合並期間發生。合並發生在未知時間的后台,因此您無法計划。某些數據可能尚未處理。盡管您可以使用OPTIMIZE查詢運行計划外合並,但不要指望使用它,因為OPTIMIZE查詢將讀取和寫入大量數據。因此,ReplacingMergeTree適合在后台清除重復數據以節省空間,但不保證不存在重復數據。

// 基於MergeTree引擎創建表
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 =MergeTree()
   order by (date_time)
  settings index_granularity = 8192;

// 基於ReplicatedReplacingMergeTree引擎創建表
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;
   
// 執行插入語句:
INSERT  into wenl_tmp_fact_general_report_production
select
data_time ,SUM(active_power) as active_power_sum,wtg_id,MAX(site_id)  as site_id
from wenl_dm_fact_daily_report_10m_detail_tmp
group by data_time, wtg_id ;

基於MergeTree引擎創建表,插入之后的效果

image-20210818110336777

基於ReplicatedReplacingMergeTree引擎創建表,插入之后的效果

image-20210818110314143

ReplicatedReplacingMergeTree引擎會在一次插入執行的時候踢出掉一些重復的數據,進行篩選

原因是我們在建表的時候,默認的排序方式order by 指定的是一個字段: date_time, 而我們這里插入的5條記錄date_time都是相同的,因此ReplicatedReplacingMergeTree引擎會判定為重復的數據,只保留一條

解決方案:

  1. 換用MergeTree引擎

  2. 或者在建表的時候,order by指定多個字段,中間用逗號隔開

 

6.java 連接Clickhouse

import ru.yandex.clickhouse.ClickHouseDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;

public class ClickhouseUtil {

   private static  Connection connection;

   public static Connection getClickHouseConnection() throws SQLException {
       String url="jdbc:clickhouse://10.65.19.56:8123/wenl1";
       ClickHouseDataSource dataSou=new ClickHouseDataSource(url);//這是官方給的創建方式
       return dataSou.getConnection("wind", "Envisi0n4321!");
  }

   public static void createTable() throws SQLException, ClassNotFoundException {
       Class.forName("ru.yandex.clickhouse.ClickHouseDriver");
       connection = getClickHouseConnection();
       Statement statement = connection.createStatement();
       statement.executeQuery(
               "CREATE TABLE if not exists wenl1.wenl_dm_fact_daily_report_10m_detail_tmp4 on cluster elune(" +
                       "org_id String, " +
                       "site_id String, " +
                       "wtg_id String, " +
                       "data_time DateTime, " +
                       "update_time DateTime, " +
                       "wind_speed Float32, " +
                       "read_wind_speed Float32, " +
                       "tem_out Float32, " +
                       "active_power Float32, " +
                       "theory_power Float32, " +
                       "production_loss Float32" +
                       " )" +
                       "ENGINE = ReplicatedReplacingMergeTree('/clickhouse/wenl1/tables/{layer}-{shard}/ wenl_dm_fact_daily_report_10m_detail_tmp4', '{replica}')" +
                       "partition by toYYYYMM(data_time) " +
                       "order by (wtg_id,data_time) " +
                       "settings index_granularity = 8192; "
      );
  }

   public static  void write( ArrayList<HashMap<String,String>>  data2Mysql ) throws SQLException{
       PreparedStatement pstmt = connection.prepareStatement(
               "insert into wenl1.wenl_dm_fact_daily_report_10m_detail_tmp4 values(?,?,?,toDateTime(?),toDateTime(?),?,?,?,?,?,?)");
       for(HashMap<String,String> subMap : data2Mysql ){
           pstmt.setString(1,subMap.get("org_id"));
           pstmt.setString(2,subMap.get("site_id"));
           pstmt.setString(3,subMap.get("wtg_id"));
           pstmt.setString(4,subMap.get("data_time"));
           pstmt.setString(5, subMap.get("update_time"));
           pstmt.setDouble(6,Double.parseDouble(subMap.get("wind_speed"))); // "windSpeed"
           pstmt.setDouble(7,Double.parseDouble(subMap.get("read_wind_speed"))); // "readWindSpeed"
           pstmt.setDouble(8,Double.parseDouble(subMap.get("tem_out"))); // "tem_out"
           pstmt.setDouble(9,Double.parseDouble(subMap.get("active_power"))); // "active_power"
           pstmt.setDouble(10,Double.parseDouble(subMap.get("theory_power"))); // "theory_power"
           pstmt.setDouble(11,Double.parseDouble(subMap.get("production_loss"))); // "production_loss"
           pstmt.addBatch();
      }
       pstmt.executeBatch();
  }
}

 

 

 

 


免責聲明!

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



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