1.explain
方法一(20.6之前版本):
在 clickhouse 20.6 版本之前要查看 SQL 语句的执行计划需要设置日志级别为 trace 才能可以看到,并且只能真正执行 sql,在执行日志里面查看
clickhouse-client -u xxxx --password xxxxxx --send_logs_level=trace <<< 'your query sql' > /dev/null;
clickhouse-client -u xxxx --password xxxxxx --send_logs_level=trace <<< 'your query sql' > /dev/null;
方法二(20.6与20.6之后版本):explain SQL
比如:
explain plan <sql语句>;
比如:
explain plan <sql语句>;
1.1 explain类型组成
AST
用于查看语法树,不常使用
SYNTAX
查询优化后的 SQL 语句(使用较多
)
QUERY TREE
PLAN
用于查看执行计划,可以指定五个参数
参数 | 描述 |
---|---|
header | 打印计划中各个步骤的 header 说明,默认 0(关闭) |
description | 打印计划中各个步骤的描述,默认 1(开启) |
indexes | 显示索引使用情况,默认 0(关闭) |
actions | 打印计划中各个步骤的详细信息,默认 0(关闭) |
json | 以 JSON 格式打印执行计划的详细信息,默认 0(关闭) |
EXPLAIN SELECT sum(number) FROM numbers(10) GROUP BY number % 4;
EXPLAIN json = 1, description = 0 SELECT 1 UNION ALL SELECT 2 FORMAT TSVRaw;
EXPLAIN SELECT sum(number) FROM numbers(10) GROUP BY number % 4;
EXPLAIN json = 1, description = 0 SELECT 1 UNION ALL SELECT 2 FORMAT TSVRaw;
PIPELINE
用于查看 pipeline 计划
header
:打印计划中各个步骤的 header 说明,默认 0(关闭);graph
:用 DOT 图形语言描述管道图,默认 0(关闭);compact
:如果开启了 graph,紧凑打印行,默认 1(开启);
1.2 语法
EXPLAIN [AST | SYNTAX | PLAN | PIPELINE | TABLE OVERRIDE] [setting = value, ...]
[
SELECT ... |
tableFunction(...) [COLUMNS (...)] [ORDER BY ...] [PARTITION BY ...] [PRIMARY KEY] [SAMPLE BY ...] [TTL ...]
]
[FORMAT ...]
EXPLAIN [AST | SYNTAX | PLAN | PIPELINE | TABLE OVERRIDE] [setting = value, ...]
[
SELECT ... |
tableFunction(...) [COLUMNS (...)] [ORDER BY ...] [PARTITION BY ...] [PRIMARY KEY] [SAMPLE BY ...] [TTL ...]
]
[FORMAT ...]
1.3 案例
1.plan
explain [plan] select arrayJoin([1,2,3,null,null]);
explain select database,table,count(1) cnt from system.parts where database in ('datasets','system') group by database,table order by database,cnt desc limit 2 by database;
explain plan header=1, actions=1,description=1 select number from system.numbers limit 10;
explain [plan] select arrayJoin([1,2,3,null,null]);
explain select database,table,count(1) cnt from system.parts where database in ('datasets','system') group by database,table order by database,cnt desc limit 2 by database;
explain plan header=1, actions=1,description=1 select number from system.numbers limit 10;
2.syntax
-- 先做一次查询
SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'xyz') FROM numbers(10);
-- 第一次查看语法优化
EXPLAIN SYNTAX SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'xyz') FROM numbers(10);
-- 返回优化后的语句
SELECT if(number = 1, 'hello', if(number = 2, 'world', 'xyz')) FROM numbers(10);
-- 开启三元运算符优化
SET optimize_if_chain_to_multiif = 1;
-- 再次查看语法优化
EXPLAIN SYNTAX SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'xyz') FROM numbers(10);
-- 返回优化后的语句
SELECT multiIf(number = 1, 'hello', number = 2, 'world', 'xyz') FROM numbers(10);
-- 先做一次查询
SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'xyz') FROM numbers(10);
-- 第一次查看语法优化
EXPLAIN SYNTAX SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'xyz') FROM numbers(10);
-- 返回优化后的语句
SELECT if(number = 1, 'hello', if(number = 2, 'world', 'xyz')) FROM numbers(10);
-- 开启三元运算符优化
SET optimize_if_chain_to_multiif = 1;
-- 再次查看语法优化
EXPLAIN SYNTAX SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'xyz') FROM numbers(10);
-- 返回优化后的语句
SELECT multiIf(number = 1, 'hello', number = 2, 'world', 'xyz') FROM numbers(10);
3.pipeline
EXPLAIN PIPELINE SELECT sum(number) FROM numbers_mt(100000) GROUP BY number % 20;
--打开其他参数
EXPLAIN PIPELINE header=1,graph=1 SELECT sum(number) FROM numbers_mt(10000) GROUP BY number%20;
EXPLAIN PIPELINE SELECT sum(number) FROM numbers_mt(100000) GROUP BY number % 20;
--打开其他参数
EXPLAIN PIPELINE header=1,graph=1 SELECT sum(number) FROM numbers_mt(10000) GROUP BY number%20;
2. 建表优化
2.1 数据类型
1.时间类型字段
- ClickHouse 中建表时不建议使用string
- ClickHouse 中建表时日期、时间字段最好设置成对应的 Date 、Datetime 类型,避免后续需要经过函数转换处理,执行效率高、可读性好
create table t_type2(
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);
create table t_type2(
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);
2.空值类型字段
ClickHouse 中使用 Nullable 来设置字段可以有空值,但是会拖累性能
存储 Nullable 列时需要创建一个额外的文件来存储 NULL 的标记,增加消耗;Nullable 列无法被索引
生产上建议:使用字段默认值或者在业务中无意义的值来表示空值
2.2 分区和索引
建表时要考虑设置分区,避免全表扫描,一般选择按天分区,如果不按天分区,则建议以单表一亿数据为例,分区大小控制在 10-30 个为最佳
建表时必须指定 order by 字段,即索引、排序列;在指定多个索引列时将查询频率大的字段放最前面,然后基数特别大的字段不适合做索引列,如用户表的 userid 字段,通常筛选后的数据满足在百万以内为最佳
2.3 表参数
- Index_granularity 是用来控制索引粒度的,默认是 8192,如非必须不建议调整
- 指定 TTL:对于不必须保存历史全量数据的表,建议指定对应的 TTL,可以免去手动过期历史数据的麻烦,TTL 也可以通过 alter table 语句随时修改
2.4 写入和删除优化
尽量不要执行单条或小批量删除和插入操作,这样会产生小分区文件,给后台 Merge 任务带来巨大压力
不要一次写入太多分区,或数据写入太快,数据写入太快会导致 Merge 速度跟不上而报错,一般建议每秒钟发起 2-3 次写入操作,每次操作写入 2w~5w 条数据(依服务器性能而定)
写入过快报错(Too many parts 和 Memory limit)处理:
- “Too many parts 处理”:使用 WAL 预写日志,提高写入性能,in_memory_parts_enable_wal 默认为 true
- 在服务器内存充裕的情况下增加内存配额,一般通过 max_memory_usage 来实现
- 在服务器内存不充裕的情况下,建议将超出部分内容分配到系统硬盘上,但会降低执行速度,一般通过 max_bytes_before_external_group_by、max_bytes_before_external_sort 参数来实现
3. ck查询优化
1.索引优化:clickhouse 的存储结构决定对于大数据量查询时,使用主键索引能够精确的找到所需的数据块,减少不必要的数据块扫描,这样更够极大的提高查询效率。将用户pin作为一级索引,将时间作为二级索。
2.填充有空值的字段:对于一些表字段,若存在空值,则可以考虑使用无业务场景意义的字符进行填充。
3.主键查询:减少查询字段:将select * 改为查询关键字段,select operate_time,id。
4.多线程:多条活动数据,多线程查询出活动和奖励的相关信息。
5.分区存储:将每7天数据放入一个分区中,查询时候根据不同时间查询不同分区的数据。
6.count优化:因为clickhouse对每个表的数据量,在底层文件中提供了预数据。所以能直接使用count()则避免使用count(col_name)。
7.聚合外推:如将sum(money * 2) 变成 sum(money) * 2。对数据库来说,后者的计算量明显少一点。
8.高级函数:ClickHouse中有很多很好用的函数。如:使用multiIf()替代多重case when,对于版本数据的获取使用argMax()函数,而非用子查询关联取最大值**。**
https://blog.csdn.net/weixin_44480009/article/details/139922122