ClickHouse开发规范

一、建表规范

  • 数据表必须设置主键
  • 数据表必须有以下字段

created_date

updated_date

created_by

updated_by

1.1 数据表引擎选择

Log系列

Log系列表引擎功能相对简单,主要用于快速写入小表(1百万行左右的表),然后全部读出的场景。

几种Log表引擎的共性是:

  • 数据被顺序append写到磁盘上。
  • 不支持delete、update。
  • 不支持index。
  • 不支持原子性写。
  • insert会阻塞select操作。

它们彼此之间的区别是:

  • TinyLog:不支持并发读取数据文件,查询性能较差;格式简单,适合用来暂存中间数据。
  • StripLog:支持并发读取数据文件,查询性能比TinyLog好;将所有列存储在同一个大文件中,减少了文件个数。
  • Log:支持并发读取数据文件,查询性能比TinyLog好;每个列会单独存储在一个独立文件中。

Integration系列

该系统表引擎主要用于将外部数据导入到ClickHouse中,或者在ClickHouse中直接操作外部数据源。

  • Kafka:将Kafka Topic中的数据直接导入到ClickHouse。
  • MySQL:将Mysql作为存储引擎,直接在ClickHouse中对MySQL表进行select等操作。
  • JDBC/ODBC:通过指定jdbc、odbc连接串读取数据源。
  • HDFS:直接读取HDFS上的特定格式的数据文件;

Special系列

Special系列的表引擎,大多是为了特定场景而定制的。这里也挑选几个简单介绍,不做详述。

  • Memory:将数据存储在内存中,重启后会导致数据丢失。查询性能极好,适合于对于数据持久性没有要求的1亿一下的小表。在ClickHouse中,通常用来做临时表。
  • Buffer:为目标表设置一个内存buffer,当buffer达到了一定条件之后会flush到磁盘。
  • File:直接将本地文件作为数据存储。
  • Null:写入数据被丢弃、读取数据为空。

MergeTree系列

Log、Special、Integration主要用于特殊用途,场景相对有限。MergeTree系列才是官方主推的存储引擎,支持几乎所有ClickHouse核心功能。

以下重点介绍MergeTree、ReplacingMergeTree、CollapsingMergeTree、VersionedCollapsingMergeTree、SummingMergeTree、AggregatingMergeTree引擎。

MergeTree

MergeTree表引擎主要用于海量数据分析,支持数据分区、存储有序、主键索引、稀疏索引、数据TTL等。MergeTree支持所有ClickHouse SQL语法,但是有些功能与MySQL并不一致,比如在MergeTree中主键并不用于去重.MergeTree虽然有主键索引,但是其主要作用是加速查询,而不是类似MySQL等数据库用来保持记录唯一。即便在Compaction完成后,主键相同的数据行也仍旧共同存在。

ReplacingMergeTree

为了解决MergeTree相同主键无法去重的问题,ClickHouse提供了ReplacingMergeTree引擎,用来做去重。

虽然ReplacingMergeTree提供了主键去重的能力,但是仍旧有以下限制:

  • 在没有彻底optimize之前,可能无法达到主键去重的效果,比如部分数据已经被去重,而另外一部分数据仍旧有主键重复。
  • 在分布式场景下,相同primary key的数据可能被sharding到不同节点上,不同shard间可能无法去重。
  • optimize是后台动作,无法预测具体执行时间点。
  • 手动执行optimize在海量数据场景下要消耗大量时间,无法满足业务即时查询的需求。

因此ReplacingMergeTree更多被用于确保数据最终被去重,而无法保证查询过程中主键不重复。

CollapsingMergeTree

ClickHouse实现了CollapsingMergeTree来消除ReplacingMergeTree的功能限制。该引擎要求在建表语句中指定一个标记列Sign,后台Compaction时会将主键相同、Sign相反的行进行折叠,也即删除。

CollapsingMergeTree将行按照Sign的值分为两类:Sign=1的行称之为状态行,Sign=-1的行称之为取消行。

每次需要新增状态时,写入一行状态行;需要删除状态时,则写入一行取消行。

在后台Compaction时,状态行与取消行会自动做折叠(删除)处理。而尚未进行Compaction的数据,状态行与取消行同时存在。

因此为了能够达到主键折叠(删除)的目的,需要业务层进行适当改造:

  • 执行删除操作需要写入取消行,而取消行中需要包含与原始状态行主键一样的数据(Sign列除外)。所以在应用层需要记录原始状态行的值,或者在执行删除操作前先查询数据库获取原始状态行。
  • 由于后台Compaction时机无法预测,在发起查询时,状态行和取消行可能尚未被折叠;另外,ClickHouse无法保证primary key相同的行落在同一个节点上,不在同一节点上的数据无法折叠。因此在进行count(*)、sum(col)等聚合计算时,可能会存在数据冗余的情况。为了获得正确结果,业务层需要改写SQL,将count()、sum(col)分别改写为sum(Sign)、sum(col * Sign)。

CollapsingMergeTree虽然解决了主键相同的数据即时删除的问题,但是状态持续变化且多线程并行写入情况下,状态行与取消行位置可能乱序,导致无法正常折叠。

VersionedCollapsingMergeTree

为了解决CollapsingMergeTree乱序写入情况下无法正常折叠问题,VersionedCollapsingMergeTree表引擎在建表语句中新增了一列Version,用于在乱序情况下记录状态行与取消行的对应关系。主键相同,且Version相同、Sign相反的行,在Compaction时会被删除。

与CollapsingMergeTree类似, 为了获得正确结果,业务层需要改写SQL,将count()、sum(col)分别改写为sum(Sign)、sum(col * Sign)。

SummingMergeTree

ClickHouse通过SummingMergeTree来支持对主键列进行预先聚合。在后台Compaction时,会将主键相同的多行进行sum求和,然后使用一行数据取而代之,从而大幅度降低存储空间占用,提升聚合计算性能。

值得注意的是:

  • ClickHouse只在后台Compaction时才会进行数据的预先聚合,而compaction的执行时机无法预测,所以可能存在部分数据已经被预先聚合、部分数据尚未被聚合的情况。因此,在执行聚合计算时,SQL中仍需要使用GROUP BY子句。
  • 在预先聚合时,ClickHouse会对主键列之外的其他所有列进行预聚合。如果这些列是可聚合的(比如数值类型),则直接sum;如果不可聚合(比如String类型),则随机选择一个值。
  • 通常建议将SummingMergeTree与MergeTree配合使用,使用MergeTree来存储具体明细,使用SummingMergeTree来存储预先聚合的结果加速查询。

AggregatingMergeTree

AggregatingMergeTree也是预先聚合引擎的一种,用于提升聚合计算的性能。与SummingMergeTree的区别在于:SummingMergeTree对非主键列进行sum聚合,而AggregatingMergeTree则可以指定各种聚合函数。

AggregatingMergeTree的语法比较复杂,需要结合物化视图或ClickHouse的特殊数据类型AggregateFunction一起使用。在insert和select时,也有独特的写法和要求:写入时需要使用-State语法,查询时使用-Merge语法。

1.2 数据表字段选择

所有字段选择按照最小原则,字段选取从以下类型中选取

数值类型

Int类型

固定长度的整数类型又包括有符号和无符号的整数类型。

  • 有符号整数类型
类型 字节 范围
Int8 1 [-2^7 ~2^7-1]
Int16 2 [-2^15 ~ 2^15-1]
Int32 4 [-2^31 ~ 2^31-1]
Int64 8 [-2^63 ~ 2^63-1]
Int128 16 [-2^127 ~ 2^127-1]
Int256 32 [-2^255 ~ 2^255-1]

Decimal类型

有符号的定点数,可在加、减和乘法运算过程中保持精度。ClickHouse提供了Decimal32、Decimal64和Decimal128三种精度的定点数,支持几种写法:

  • Decimal(P, S)

  • 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) )

其中:P代表精度,决定总位数(整数部分+小数部分),取值范围是1~76

S代表规模,决定小数位数,取值范围是0~P

  • 两个不同精度的数据进行四则运算时,结果数据已最大精度为准

字符串类型

String

字符串可以是任意长度的。它可以包含任意的字节集,包含空字节。因此,字符串类型可以代替其他 DBMSs 中的VARCHAR、BLOB、CLOB 等类型。

UUID

UUID是一种数据库常见的主键类型,在ClickHouse中直接把它作为一种数据类型。UUID共有32位,它的格式为8-4-4-4-12,比如:

61f0c404-5cb3-11e7-907b-a6006ad3dba0
-- 当不指定uuid列的值时,填充为0
00000000-0000-0000-0000-000000000000
123

日期类型

Date类型

用两个字节存储,表示从 1970-01-01 (无符号) 到当前的日期值。日期中没有存储时区信息。

DateTime类型

用四个字节(无符号的)存储 Unix 时间戳。允许存储与日期类型相同的范围内的值。最小值为 0000-00-00 00:00:00。时间戳类型值精确到秒(没有闰秒)。时区使用启动客户端或服务器时的系统时区。

db01 :) CREATE TABLE t_date (x date,y datetime) ENGINE=TinyLog;

CREATE TABLE t_date
(
    `x` date,
    `y` datetime
)
ENGINE = TinyLog

Ok.

0 rows in set. Elapsed: 0.004 sec. 

db01 :) INSERT INTO t_date VALUES('2020-10-01','2020-10-01 00:00:00');

INSERT INTO t_date VALUES

Ok.

1 rows in set. Elapsed: 0.012 sec. 

db01 :) select * from t_date;

SELECT *
FROM t_date

┌──────────x─┬───────────────────y─┐
│ 2020-10-01 │ 2020-10-01 00:00:00 │
└────────────┴─────────────────────┘

1 rows in set. Elapsed: 0.007 sec. 

二、SQL规范

  • 禁止select *

    只查询需要的字段可以减少磁盘io和网络io,提升查询性能

  • 禁止在大结果集上构造虚拟列

    虚拟列非常消耗资源浪费性能,拿到pv uv后在前端显示时构造比率

  • 关联查询时小表在后(大表 join 小表),并必须有关联条件(clickhouse建议控制在两张表以内的join)

    无论是Left Join 、Right Join还是Inner Join永远都是拿着右表中的每一条记录到左表中查找该记录是否存在

  • 使用 uniqCombined 替代 distinct

    uniqCombined对去重进行了优化,通过近似去重提升十倍查询性能

  • 建议查询指定分区

    通过指定分区字段会减少底层数据库扫描的文件数量,提升查询性能

  • 建议使用 limit 限制返回数据条数

    使用limit返回指定的结果集数量,不会进行向下扫描,大大提升了查询效率

猜你喜欢

转载自blog.csdn.net/qq_42979842/article/details/109785784