ClickHouse
介绍
ClickHouse 是俄罗斯的 Yandex 于 2016 年开源的用于在线分析处理查询(OLAP)MPP架构的列式存储数据库(DBMS),能够使用 SQL 查询实时生成分析数据报告。
列式存储
- 对于列的聚合、计数、求和等统计操作优于行式存储
- 由于某一列的数据类型都是相同的,针对于数据存储更容易进行数据压缩,每一列选择更优的数据压缩算法,大大提高了数据的压缩比重
- 数据压缩比更好,一方面节省了磁盘空间,另一方面对于cache也有了更大的发挥空间
- 列式存储不支持事务
- 单独设置表的分区存储
多样化引擎
-
MergeTree系列引擎
适用于高负载任务的最通用和功能最强大的表引擎。这些引擎的共同特点是可以快速插入数据并进行后续的后台数据处理 -
日志
具有最小功能的轻量级引擎。当您需要快速写入许多小表(最多约100万行)并在以后整体读取它们时,该类型的引擎是最有效的。
-
集成引擎
- 详细参考:https://clickhouse.com/docs/zh/engines/table-engines/
限制
- 没有完整的事务支持。
- 缺少高频率,低延迟的修改或删除已存在数据的能力。仅能用于批量删除或修改数据,但这符合 GDPR。
- 稀疏索引使得ClickHouse不适合通过其键检索单行的点查询。
安装 docker 版
# 拉取镜像 docker pull yandex/clickhouse-server # 启动镜像 docker run -d --name ch-server --ulimit nofile=262144:262144 -p 9000:9000 -p 8123:8123 yandex/clickhouse-server
参考:https://www.jianshu.com/p/921a0d82c7b8 其他版本安装:https://clickhouse.com/docs/zh/getting-started/install
create
- 创建数据库
CREATE DATABASE [IF NOT EXISTS] db_name # 如果查询中存在 IF NOT EXISTS,则当数据库表已经存在时,该查询不会返回任何错误
- 创建表
create table if not exists test.tb_test ( id Int64, datetime DateTime, content Nullable(String), value Nullable(Float64), date Date ) engine = MergeTree --使用mergeTree引擎,ch主要引擎 partition by toYYYYMM(datetime) --按照datetime这个字段的月进行分区 order by id --按照id进行排序 TTL datetime + INTERVAL 3 DAY ; --三天过期
MergeTree引擎特点:
1. 按主键排序,创建一个小型的稀疏索引来加快数据检索; 2. 指定了 分区键 的话,查询中指定了分区键时 ClickHouse 会自动截取分区数据,这也有效增加了查询性能;
partition by分区(可选)
1. 和 hive 一样,分区的目的主要是降低扫描的范围,优化查询速度; 2. 如果不填,只会使用一个分区; 3. MergeTree 是以列文件+索引文件+表定义文件组成的,但是如果设定了分区那么这些文件就会保存到不同的分区目录中 4. 分区后,面对涉及跨分区的查询统计,ClickHouse 会以分区为单位并行处理;
primary key 主键(可选)
1. 和其他数据库不太一样,它只提供了数据的一级索引,但是却不是唯一约束。这就意味着是可以存在相同 primary key 的数据的。 2. 主键的设定主要依据是查询语句中的 where 条件。 3. 根据条件通过对主键进行某种形式的二分查找,能够定位到对应的 index granularity,避免了全表扫描。 4. 稀疏索引的好处就是可以用很少的索引数据,定位更多的数据,代价就是只能定位到索引粒度的第一行,然后再进行进行一点扫描。
order by(该引擎必选)
1. 定义了分区内的数据按照哪些字段顺序进行有序保存。 2. 不设置主键的情况,很多处理会依照 order by 的字段进行处理 3. 比如 order by 字段是 (id,sku_id) 那么主键必须是 id 或者(id,sku_id)。
列和表的生命周期(TTL)
TTL用于设置值的生命周期,它既可以为整张表设置,也可以为每个列字段单独设置。表级别的 TTL 还会指定数据在磁盘和卷上自动转移的逻辑。
# 列 TTL date_time + INTERVAL 1 MONTH TTL date_time + INTERVAL 15 HOUR # 更改现有表中的列过期时间 # 表结构 CREATE TABLE example_table ( d DateTime, a Int TTL d + INTERVAL 1 MONTH, b Int TTL d + INTERVAL 1 MONTH, c String ) ENGINE = MergeTree PARTITION BY toYYYYMM(d) ORDER BY d; # 添加 ALTER TABLE example_table MODIFY COLUMN c String TTL d + INTERVAL 1 DAY; # 更改 ALTER TABLE example_table MODIFY COLUMN c String TTL d + INTERVAL 1 MONTH;
参考 https://clickhouse.com/docs/zh/sql-reference/statements/create/
insert into
- 基本格式
INSERT INTO [db.]table [(c1, c2, c3)] VALUES (v11, v12, v13), (v21, v22, v23), ...
- 模糊字段插入
使用列匹配器的表达式,例如*和/或修饰符,例如 APPLY, EXCEPT, REPLACE。
INSERT INTO insert_select_testtable (*) VALUES (1, 'a', 1) ;
如果要在除了'b'列以外的所有列中插入数据,您需要传递和括号中选择的列数一样多的值:
INSERT INTO insert_select_testtable (* EXCEPT(b)) Values (2, 2);
-
性能的注意事项
在进行INSERT时将会对写入的数据进行一些处理,按照主键排序,按照月份对数据进行分区等。所以如果在您的写入数据中包含多个月份的混合数据时,
将会显著的降低INSERT的性能。为了避免这种情况: • 数据总是以尽量大的batch进行写入,如每次写入100,000行。 • 数据在写入ClickHouse前预先的对数据进行分组。 在以下的情况下,性能不会下降: • 数据总是被实时的写入。 • 写入的数据已经按照时间排序。 也可以异步的、小规模的插入数据,这些数据会被合并成多个批次,然后安全地写入到表中,通过设置async_insert,可以使用异步插入的方式,请注意,
异步插入的方式只支持HTTP协议,并且不支持数据去重。
参考:https://clickhouse.com/docs/zh/sql-reference/statements/insert-into
select
-
模糊匹配字段查询
SELECT COLUMNS('a'), COLUMNS('c'), toTypeName(COLUMNS('c')) FROM col_names 匹配字段名中有‘a’,’c’的,和打印‘c’列的类型
- with
表结构和数据
clickhouse1 :) create table select_table_test( :-] id UInt32, :-] name String, :-] age UInt8, :-] city String, :-] score Float64 :-] ) engine = MergeTree() :-] order by (id, intHash32(id)) :-] sample by intHash32(id); clickhouse1 :) clickhouse1 :) insert into select_table_test(id, name, age, city, score) values(1, 'Zhangsan', 30, 'Beijing', 70); clickhouse1 :) insert into select_table_test(id, name, age, city, score) values(2, 'Lisi', 40, 'Shanghai', 80); clickhouse1 :) insert into select_table_test(id, name, age, city, score) values(3, 'Wangwu', 50, 'Guangzhou', 90); clickhouse1 :) clickhouse1 :) select * from select_table_test; ┌─id─┬─name─────┬─age─┬─city────┬─score─┐ │ 1 │ Zhangsan │ 30 │ Beijing │ 70 │ └────┴──────────┴─────┴─────────┴───────┘ ┌─id─┬─name─┬─age─┬─city─────┬─score─┐ │ 2 │ Lisi │ 40 │ Shanghai │ 80 │ └────┴──────┴─────┴──────────┴───────┘ ┌─id─┬─name───┬─age─┬─city──────┬─score─┐ │ 3 │ Wangwu │ 50 │ Guangzhou │ 90 │ └────┴────────┴─────┴───────────┴───────┘
# 查询age等于40的所有结果 with 40 as fit_age select * from select_table_test where age = fit_age; # 以city分组,查询score的和,转成str一列,转成int16一列 with sum(score) as sum_score select city, toString(sum_score), toUInt16(sum_score) from select_table_test group by city; # 查询表中所有数据,条件为 age等于 (city=Beijing的所有age) with (select age from select_table_test where city = 'Beijing') as fit_age select * from select_table_test where age = fit_age;
-
array join
表结构
clickhouse1 :) create table array_join_table( :-] fruit String, :-] values Array(UInt8), :-] city String, :-] values2 Array(UInt8) :-] ) engine = TinyLog; clickhouse1 :) clickhouse1 :) insert into array_join_table(fruit, values, city, values2) values('banana', [1,2,3], 'Hainan', [18,19]); clickhouse1 :) insert into array_join_table(fruit, values, city, values2) values('orange', [66,88], 'Hunan', [6,7,8,9]); clickhouse1 :) insert into array_join_table(fruit, values, city, values2) values('apple',[], 'Shangxi', [11,12,13]); clickhouse1 :) clickhouse1 :) select * from array_join_table; ┌─fruit──┬─values──┬─city────┬─values2────┐ │ banana │ [1,2,3] │ Hainan │ [18,19] │ │ orange │ [66,88] │ Hunan │ [6,7,8,9] │ │ apple │ [] │ Shangxi │ [11,12,13] │ └────────┴─────────┴─────────┴────────────┘
# 查询 values 列的所有数据,使用arry join会把列内的一行转成多行(行转列)
select fruit, values from array_join_table array join values; ┌─fruit──┬─values─┐ │ banana │ 1 │ │ banana │ 2 │ │ banana │ 3 │ │ orange │ 66 │ │ orange │ 88 │ └────────┴────────┘
# 行转列,放到别名列 select fruit, values, v from array_join_table array join values as v; ┌─fruit──┬─values──┬──v─┐ │ banana │ [1,2,3] │ 1 │ │ banana │ [1,2,3] │ 2 │ │ banana │ [1,2,3] │ 3 │ │ orange │ [66,88] │ 66 │ │ orange │ [66,88] │ 88 │ └────────┴─────────┴────┘
排除掉了空数组 - left array join
select fruit, values, v from array_join_table left array join values as v; ┌─fruit──┬─values──┬──v─┐ │ banana │ [1,2,3] │ 1 │ │ banana │ [1,2,3] │ 2 │ │ banana │ [1,2,3] │ 3 │ │ orange │ [66,88] │ 66 │ │ orange │ [66,88] │ 88 │ │ apple │ [] │ 0 │ └────────┴─────────┴────┘
# 使用公式计算values中列表的值,在方到别名列中 select fruit, values, v, arrayMap(x -> x * 2, values) as map_values, m_v from array_join_table left array join values as v, map_values as m_v; ┌─fruit──┬─values──┬──v─┬─map_values─┬─m_v─┐ │ banana │ [1,2,3] │ 1 │ [2,4,6] │ 2 │ │ banana │ [1,2,3] │ 2 │ [2,4,6] │ 4 │ │ banana │ [1,2,3] │ 3 │ [2,4,6] │ 6 │ │ orange │ [66,88] │ 66 │ [132,176] │ 132 │ │ orange │ [66,88] │ 88 │ [132,176] │ 176 │ │ apple │ [] │ 0 │ [] │ 0 │ └────────┴─────────┴────┴────────────┴─────┘ 数组为空的出现在结果中 与多个数组字段进行array join时,在一行数据中多个数组的元素个数要相同,也是将一行数据打平至多行 - 单独
使用案例参考:https://blog.csdn.net/yy8623977/article/details/119516455
参考:https://clickhouse.com/docs/zh/sql-reference/statements/select/
alter
- 删除分区,用于定时任务删除旧数据
alter table tb_test drop partition '202005';
- 修改数据,不推荐使用(因为没有唯一条件,会导致错误)
alter table tb_test update content = 'hello click' where id=52;
- 删除数据,不推荐使用
alter table tb_test delete WHERE id=56;
参考: https://clickhouse.com/docs/zh/sql-reference/statements/alter#query_language_queries_alter