• 《ClickHouse原理解析与应用实践》读书笔记(2)


    开始学习《ClickHouse原理解析与应用实践》,写博客作读书笔记。

    本文全部内容都来自于书中内容,个人提炼。

    第一章  ->  第二章:

    《ClickHouse原理解析与应用实践》读书笔记(1)_Aiky哇的博客-CSDN博客开始学习《ClickHouse原理解析与应用实践》,写博客作读书笔记。本文全部内容都来自于书中内容,个人提炼。https://aikysay.blog.csdn.net/article/details/125441354 

    第3章 安装与部署

    【略过,网上资料很多,不用看这个】

    第4章 数据定义

    ClickHouse提供了DDL 与DML的功能,并支持大部分标准的SQL。

    ClickHouse的 UPDATE和DELETE是借助ALTER变种实现的。

    4.1 ClickHouse的数据类型

    数据类型可以划分为基础类型、复合类型特殊类型

    4.1.1 基础类型

    基础类型只有数值、字符串和时间三种类型,没有Boolean类型, 但可以使用整型的0或1替代。

    1.数值类型

    对于int。

    ck使用Int8、Int16、Int32和 Int64指代四种大小的int。数字为占用位数。

    同时还支持加前缀U的无符号类型:UInt8、UInt16、UInt32和 UInt64。

    名称大小(字节)范围普遍观念
    Int81[-128,127]Tinyint
    Int162[-32768,32767]Smallint
    Int324[-2147483648,2147483647]Int
    Int648[-9223372036854775808,9223372036854775807]Bigint
    UInt81[0,255]Tinyint Unsigned
    UInt162[0,65535]Smallint Unsigned
    UInt324[0,4294967295]Int Unsigned
    UInt648[0,18446744073709551615]Bigint Unsigned

    对于Float。

    ck使用Float32和Float64代表单精度 浮点数以及双精度浮点数。

    超过有效精度会产生数据溢出。

    ck支持正无穷、负无穷以及非数字的表达方式。

    对于Decimal。

    ck提供Decimal32、Decimal64和Decimal128三种精度的定点数。

    可以通过两种形式声明定点:

    1. 简写方式有Decimal32(S)、Decimal64(S)、 Decimal128(S)三种
    2. 原生方式为Decimal(P,S)

    其中:

    • P代表精度,决定总位数(整数部分+小数部分),取值范围是1 ~38;
    • S代表规模,决定小数位数,取值范围是0~P。

     

    名称灯效范围
    Decimal32(S)Decimal(1-9,S)[-1*10^(9-S) , 1*10^(9-S)]
    Decimal64(S)Decimal(10-18,S)[-1*10^(18-S) , 1*10^(18-S)]
    Decimal128(S)Decimal(19-38,S)[-1*10^(38-S) , 1*10^(38-S)]

    两个Decimal加法运算时,S取最大值。

    两个Decimal减法运算时,S取最大值。

    两个Decimal乘法运算时,S取两者S之和。

    两个Decimal除法运算时,S取被除数的值,此时要求被除数S必须大于 除数S,否则会报错。

    现代计算器系统只支持 32位和64位CPU,所以Decimal128速度会明显慢于Decimal32与Decimal64。

    2.字符串类型

    字符串分为String、FixedString和UUID三类。

    对于String。

    长度不限,不限定字符集,但在同一套程序中应该遵循使用统一的编码。

    对于FixedString。

    Char类型有些类似。通过 FixedString(N)声明。

    使用固定长度,但与Char不同的是, FixedString使用null字节填充末尾字符,而Char通常使用空格填充。

    对于UUID。

    数据库常见的主键类型。

    UUID共有32位,它的格式为8-4-4-4-12。

    如果一个UUID类型的字段在写入数据时没有被赋值,则会依照格式使用0填充。

    3.时间类型

    分为DateTime、DateTime64和Date三类。

    ClickHouse目前没有时间戳类型。

    对于DateTime。

    DateTime类型包含时、分、秒信息,精确到秒,支持使用字符串形式写入。

    对于DateTime64。

    DateTime64可以记录亚秒,它在DateTime之上增加了精度的设置。

    对于Date。

    只精确到天。

    4.1.2 复合类型

    ClickHouse还提供了数组、元组、枚举和嵌套四类复合类型。

    1.Array

    数组有两种定义形式:

    1. 常规方式array(T)
    2. 者简写方式[T]

     ck数组拥有类型推断的能力。推断依据:以最小存储代价为原则,即使用最小可表达的数据类型。

    在定义表字段时,数组需要指定明确的元素类型。

    1. CREATE TABLE Array_TEST (
    2. c1 Array(String)
    3. ) engine = Memory

    2.Tuple

    由1~n个元素组成,每个元素彼此之间不要求兼容。

    同样支持类型推断,其推断依据仍然以最小存储代价为原则。

    元组有两种定义形式:

    1. 常规方式tuple(T)
    2. 简写方式(T)

     在定义表字段时,元组需要指定明确的元素类型。

    1. CREATE TABLE Tuple_TEST (
    2. c1 Tuple(String,Int8)
    3. ) ENGINE = Memory;

    写入INSERT INTO Tuple_TEST VALUES(('abc',123))是可行的,而写入INSERT INTO Tuple_TEST VALUES(('abc','efg'))则会报错。

    3.Enum

    定义常量时经常会使用。

    ck提供Enum8 和Enum16两种枚举类型,

    枚举固定使用(String:Int)Key/Value 键值对的形式定义数据,所以Enum8和Enum16分别会对应(String:Int8)和(String:Int16)。

    Key和Value是不允许重复的,要保证唯一性。

    Key和Value的值都不能为Null,但Key允许是空字符串。

    在写入枚举数据的时候,只会用到Key字符串部分。

    1. CREATE TABLE Enum_TEST (
    2. c1 Enum8('ready' = 1, 'start' = 2, 'success' = 3, 'error' = 4)
    3. ) ENGINE = Memory;
    4. INSERT INTO Enum_TEST VALUES('ready');
    5. INSERT INTO Enum_TEST VALUES('start');

    insert时会对照枚举集合项的内容逐一检查。如果Key字符串不在集合范围内则会抛出异常。

    enum可在查询中提升性能。

    虽然枚举定义中的Key属于String类型,但是在后续对枚举的所有操作中(包括排序、分组、 去重、过滤等),会使用Int类型的Value值。

    4.Nested

    嵌套类型本质是一种多维数组的结构。嵌套表中的每个字段都是一个数组。

    行与行之间数组的长度无须对齐。但是在同一行数据内每个数组字段的长度必须相等。

    1. CREATE TABLE nested_test (
    2. name String,
    3. age UInt8 ,
    4. dept Nested(
    5. id UInt8,
    6. name String
    7. )
    8. ) ENGINE = Memory;
    9. INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001,10002], ['研发部','技术支持中心','测试部']);
    10. --行与行之间,数组长度无须对齐
    11. INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001], ['研发部','技术支持中心']);
    12. --INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001], ['研发部','技术支持中心','测试部']);
    13. --DB::Exception: Elements 'dept.id' and 'dept.name' of Nested data structure 'dept' (Array columns) have different

    4.1.3 特殊类型

    1.Nullable

    辅助修饰符。需要与基础数据类型一起搭配使用(不能用于数组和元组这些复合类型,也不能作为 索引字段),表示某个基础数据类型可以是Null值。

    慎用Nullable。会使查询和写入性能变慢。

    因为列字段会有专门保存null值的文件。

    2.Domain

    域名类型分为IPv4和IPv6两类,本质上它们是对整型和字符串的进一步封装。

    IPv4类型比string的好处:

    1. IPv4类型是基于UInt32封装的,支持格式检查。
    2. IPv4类型使用UInt32存储, 相比String更加紧凑,占用的空间更小,查询性能更快。

    另外IPv6类型是 基于FixedString(16)封装。

    但Domain类型并不是字符串,所以不支持隐式转换,需要返回IP的字符串形式,需要显式调用 IPv4NumToString或IPv6NumToString函数进行转换。

     

    4.2 如何定义数据表

    DDL查询提供了数据表的创建、修改和删 除操作。

    4.2.1 数据库

    数据库起到命名空间的作用,可以有效规避命名冲突。支持了数据隔离。

    CREATE DATABASE IF NOT EXISTS db_name [ENGINE = engine]

    【这块内容已经过时,书上只列了5个引擎。现在已经8个了,建议去官网看数据库的各个引擎介绍。Database Engines | ClickHouse Docs

    在创建数据库后,物理磁盘上会生成一个文件目录。

     这里创建了一个aikytest1的数据库。

    去ck的数据目录下查看,发现确实有个文件夹。因为没建表,文件夹是空的。

     去metadata下,会一同创建用于恢复数据库sql文件。

     4.2.2 数据表

    第一种是常规定义方法。

    默认会使用 default数据库。

    1. CREATE TABLE [IF NOT EXISTS] [db_name.]table_name (
    2. name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
    3. name2 [type] [DEFAULT|MATERIALIZED|ALIAS expr], 省略…
    4. ) ENGINE = engine

    末尾的ENGINE参数, 它被用于指定数据表的引擎。

    表引擎决定了数据表的特性,也决定了数据将会被如何存储及加载。

    第二种是复制其他表的结构。

    CREATE TABLE [IF NOT EXISTS] [db_name1.]table_name AS [db_name2.] table_name2 [ENGINE = engine]

    支持在不同的数据库之间复制表结构。AS后的复制给AS前的。

    ENGINE 表引擎可以与原表不同。

    第三种是通过SELECT子句的形式创建。

    CREATE TABLE [IF NOT EXISTS] [db_name.]table_name ENGINE = engine AS SELECT

    不仅会根据SELECT子句建立相应的表结构,同时还会将SELECT子句 查询的数据顺带写入。

    ENGINE 表引擎可以与原表不同。

    【现在官网还有使用表函数的方式From a Table Function】

    CREATE TABLE [IF NOT EXISTS] [db.]table_name AS table_function()

    创建一个与指定的表函数具有相同结果的表。 创建的表也将以与指定的相应表函数相同的方式工作。

    4.2.3 默认值表达式

    表字段支持三种默认值表达式的定义方法,分别是DEFAULT、 MATERIALIZED和ALIAS。

    表字段被定义了默认值,不再强制要求定义数据类型,因为可以根据默认值进行类型推断。如果定义了则以明确定义的数据类型为主。

    三种定义方法的不同:

    1. 数据写入时。只有DEFAULT类型可用,MATERIALIZED和ALIAS都不能被显式赋值,只能依赖计算。
    2. 数据查询时。只有DEFAULT类型可SELECT *时返回。MATERIALIZED和ALIAS类型的字段不会出现在SELECT *查询的返回结果集中。
    3. 数据存储时。只有DEFAULT和MATERIALIZED类型的字段才支持持久化。ALIAS类型的字段不支持持久化,它的取值总是需要依靠计算产生,数据不会落到磁盘。

    可以使用ALTER语句修改默认值:

    ALTER TABLE [db_name.]table MODIFY COLUMN col_name DEFAULT value

    修改动作并不会影响数据表内先前已经存在的数据。

    4.2.4 临时表

    创建临时表的方法是在普通表的基础之上添加TEMPORARY关键字。

    1. CREATE TEMPORARY TABLE [IF NOT EXISTS] table_name (
    2. name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
    3. name2 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
    4. )

    临时表的特殊处:

    1. 声明周期和会话绑定,只支持Memory表引擎。
    2. 不属于任何数据库。

    临时表的优先级是大于普通表的,同名情况下优先选择临时表。

    更多被运用在ClickHouse的内部,是数据在集群间传播的载体。

    4.2.5 分区表

    数据分区(partition)和数据分片(shard)是完全不同的两个概念。

    数据分区是针对本地数据而言的,是数据的一种纵向切分。

    数据分片是数据的一种横向切分。

    目前只有合并树(MergeTree)家族系列的表引擎才支持数据分区。由PARTITION BY指定分区键。

    举例如下:

    1. CREATE TABLE partition_v1 (
    2. ID String,
    3. URL String,
    4. EventTime Date
    5. ) ENGINE = MergeTree()
    6. PARTITION BY toYYYYMM(EventTime)
    7. ORDER BY ID;
    8. INSERT INTO partition_v1 VALUES
    9. ('A000','www.nauu.com', '2019-05-01'),
    10. ('A001','www.brunce.com', '2019-06-02');

    通过system.parts系统表,查询数据表的分区状态:

    SELECT table,partition,path from system.parts WHERE table = 'partition_v1'
    

     按年月划分后,目前拥有两个数据分区,每个分区都对应一个独立的文件目录,用于保存各自部分的数据。

    如果后续的查询按照分区键过滤,可以利用分区索引跳过6月份的分区目录。带来查询的性能提升。

    分区键不应该使用粒度过细的数据字段。

    4.2.6 视图

    ClickHouse拥有普通和物化两种视图,其中物化视图拥有独立的存储,而普通视图只是一层简单的查询代理。

    创建普通视图:

    CREATE VIEW [IF NOT EXISTS] [db_name.]view_name AS SELECT ...

    普通视图不会存储任何数据,它只是一层单纯的SELECT查询映射,起着简化查询、明晰语义的作用, 对查询性能不会有任何增强。

    物化视图支持表引擎,数据保存形式由它的表引擎决定,创建物化视图的完整语法如下所示:

    CREATE [MATERIALIZED] VIEW [IF NOT EXISTS] [db.]table_name [TO[db.]name] [ENGINE = engine] [POPULATE] AS SELECT .

    物化视图创建好之后,如果源表被写入新数据,那么物化视图也会同步更新。

    POPULATE修饰符决定了 物化视图的初始化策略:如果使用了POPULATE修饰符,那么在创建视图的过程中,会连带将源表中已存在的数据一并导入;如果不使用POPULATE修饰符,那么物化视图在创 建之后是没有数据的,它只会同步在此之后被写入源表的数据。

    物化视图目前并不支持同步删除,如果在源表中删除了数据,物化视图的数据仍会保留。

    物化视图本质是一张特殊的数据表。所以删除视图直接使用DROP TABLE查询。

    4.3 数据表的基本操作

    4.3.1 追加新字段

    1. --追加新字段
    2. ALTER TABLE tb_name ADD COLUMN [IF NOT EXISTS] name [type] [default_expr] [AFTER name_after]
    3. --举例:
    4. --末尾增加字段:
    5. ALTER TABLE testcol_v1 ADD COLUMN OS String DEFAULT 'mac'
    6. --指定字段的后面增加新字段:
    7. ALTER TABLE testcol_v1 ADD COLUMN IP String AFTER ID

    对于数据表中已经存在的旧数据而言,新追加的字段会使用默认值补全。

    4.3.2 修改数据类型 

    ALTER TABLE tb_name MODIFY COLUMN [IF EXISTS] name [type] [default_expr]
    

    修改某个字段的数据类型,会调用toType转型方法。

    如果不能兼容,则修改操作将会失败。

    4.3.3 修改备注

    追加备注的语法如下 所示:

    1. -- 追加备注的语法如下所示:
    2. ALTER TABLE tb_name COMMENT COLUMN [IF EXISTS] name 'some comment'
    3. -- 举例:
    4. ALTER TABLE testcol_v1 COMMENT COLUMN ID '主键ID'

    4.3.4 删除已有字段

    1. --删除某个字段:
    2. ALTER TABLE tb_name DROP COLUMN [IF EXISTS] name
    3. -- 举例:
    4. ALTER TABLE testcol_v1 DROP COLUMN URL

    数据也会被连带删除。

    4.3.5 移动数据表

    ck的RENAME查询和linux命令mv类似。

    1. RENAME TABLE [db_name11.]tb_name11 TO [db_name12.]tb_name12, [db_name21.]tb_name21 TO [db_name22.]tb_name22, ...
    2. -- 举例,default数据库中的testcol_v1表转移到db_test库,重命名testcol_v2:
    3. RENAME TABLE default.testcol_v1 TO db_test.testcol_v2

    数据表的移动只能在单个节点的范围内。

    4.3.6 清空数据表

    1. --不删表,清数据:
    2. TRUNCATE TABLE [IF EXISTS] [db_name.]tb_name

     

    4.4 数据分区的基本操作

    4.4.1 查询分区信息

    system.parts系统表专门用于查询数据表的分区信息。

    4.4.2 删除指定分区

    1. -- 删除某个分区
    2. ALTER TABLE tb_name DROP PARTITION partition_expr

    4.4.3 复制分区数据

    如果:

    • 两张表需要拥有相同的分区键;
    • 它们的表结构完全相同。

    那么支持将A表的分区数据复制到B表。

    ALTER TABLE B REPLACE PARTITION partition_expr FROM A

    只是复制数据,不会删掉原表分区数据。

    4.4.4 重置分区数据

    1. -- 如果数据表某一列的数据有误,需要将其重置为初始值
    2. ALTER TABLE tb_name CLEAR COLUMN column_name IN PARTITION partition_expr

    如果声明了默认值表达 式,则以表达式为准;否则以相应数据类型的默认值为准。

    4.4.5 卸载与装载分区

    表分区可以通过DETACH语句卸载,分区被卸载后,它的物理数据并没有删除,而是被转移到了当前数据表目录的detached子目录下。

    装载分区则是反向操作,它能够将detached子目录下的某个分区重新装载回去。

    卸载与装载这一对伴生的操作,常用于分区数据的迁移 和备份场景。

    1. --卸载某个分区
    2. ALTER TABLE tb_name DETACH PARTITION partition_expr

     

     一旦分区被移动到了detached子目录,就代表它已经脱离了ClickHouse的管理,ClickHouse并不会主动清理这些文件。这些分区文件会一直存在,除非我们主动删除或者使用ATTACH语句重新装载它们。

    1. -- 装载某个分区的完整语法如下所示
    2. ALTER TABLE tb_name ATTACH PARTITION partition_expr

     

     【尝试了一下发现detached文件夹没删,但是分片已经移到外面了】

    4.4.6 备份与还原分区

    可以通过FREEZE与FETCH实现。

    留到第11章专门介绍。

    4.5 分布式DDL执行

    ClickHouse支持集群模式,只需加上ON CLUSTER cluster_name声明即可。

    在集群中任意一个节点上执行DDL语句,那么集群中的 每个节点都会以相同的顺序执行相同的语句。

    1. -- 举例,在集群上执行,但是需要配置ch_cluster的集群:
    2. CREATE TABLE partition_v3 ON CLUSTER ch_cluster(
    3. ID String,
    4. URL String,
    5. EventTime Date
    6. ) ENGINE = MergeTree()
    7. PARTITION BY toYYYYMM(EventTime)
    8. ORDER BY ID

     

    4.6 数据的写入

    INSERT语句支持三种语法范式。

    第一种是使用VALUES格式的常规语法

    使用VALUES格式的语法写入数据时,支持加入表达式或函数。

    INSERT INTO [db.]table [(c1, c2, c3…)] VALUES (v11, v12, v13…), (v21, v22, v23…), ...

    第二种是使用指定格式的语法

    INSERT INTO [db.]table [(c1, c2, c3…)] FORMAT format_name data_set

    ClickHouse支持多种数据格式(更多格式可参见官方手册),以常用的CSV格式写入为例:

    1. INSERT INTO partition_v2 FORMAT CSV \
    2. 'A0017','www.nauu.com', '2019-10-01' \
    3. 'A0018','www.nauu.com', '2019-10-01'

    第三种是使用SELECT子句形式的语法

    INSERT INTO [db.]table [(c1, c2, c3…)] SELECT ...

    在通过SELECT子句写入数据的时候,同样也支持加入表达式或函数。但是表达式和函数会带来额外的性能开销,从而导致写入性能的下降。

    以INSERT查询最终会 将数据转换为Block数据块。INSERT语句在单个数据块的写入过程中是具有原子性的。

    默认,每个block最多可以写入1048576行数据(由max_insert_block_size参数控制)

    原子性只有在ClickHouse服务端处理数据的时候才具有这种原子写入的特性,例如使用JDBC或者HTTP接口时。因为max_insert_block_size参数在使用CLI命令行或者INSERT SELECT子句写入时是不生效的。

    4.7 数据的删除与修改

    ck的了DELETE和UPDATE被称为Mutation查询。可以看作ALTER语句的 变种。

    1. Mutation语句是一种“很重”的操作,更适用于批量数据的修改和删除。
    2. 不支持事务,一旦语句被提交执行,就会立刻对现有数据产生影响,无法回滚。
    3. Mutation语句的执行是一个异步的后台过程,语句被提交之后就会立即返回。具体执行进度需要通过system.mutations系统表查询。
    1. -- DELETE语句的完整语法如下所示
    2. ALTER TABLE [db_name.]table_name DELETE WHERE filter_expr

    尝试如下,删除表中一条数据:

     在数据目录下,看到每一个原有的数据目录都额外增加了 一个同名目录,并且在末尾处增加了_3的后缀。目录下还多了一个名为mutation_3.txt的文件 

     

     mutation_3.txt的文件完整地记录了这次DELETE操作的执行语句和时间,而文件名的后缀_3与新增目录的后缀对应。

    查询system.mutations系统表查看_3的来源:

     每执行一条ALTER DELETE语句,都会在mutations系统表中生成一条对应的执行计划。

    当is_done等于1时表示执行完毕。

    在数据表的根目录下,会以mutation_id为名生成与之对应的日志文件用于记录相关信息。

    数据删除的过程是以数据表的每个分区目录为单位,将所有目录重写为新的目录,新目录的命名规则是在原有名称上加上 system.mutations.block_numbers.number。

    数据在重写的过程中会将需要删除的数据去掉。

    旧的数据目录并不会立即删除,而是会被标记成非激活状态(active为0)。等到MergeTree引擎的下一次合并动作触发时,这些非激活目录才会被真正从物理意义上删除。

    数据修改和数据删除的执行流程大致相同。

    ALTER TABLE [db_name.]table_name UPDATE column1 = expr1 [, ...] WHERE filter_expr

    但是数据修改中,分区键和主键不能作为修改字段。

    4.8 本章小结

    ClickHouse的数据类型。

    数据库、数据表、临时表、 分区表和视图的基本操作以及对元数据和分区的基本操作。

    如何写入、修改和删除数据。

  • 相关阅读:
    Endpoint Central端点安全管理
    【30. 串联所有单词的子串】
    精品基于Javaweb的酒店民宿管理推荐平台SSM
    MySQL INNER JOIN:内连接查询
    OpenFlow协议原理及基本配置-网络测试仪实操
    一个更好用的.NET Core程序瘦身器,减小程序尺寸到1/3
    BGP服务器租用价格表_腾讯云PK阿里云
    暄桐林曦老师的三个“知行”心得,帮你少走弯路
    一文拿捏Spring之IOC、循环依赖、Spring的设计模式
    高级Shopify主题开发教程
  • 原文地址:https://blog.csdn.net/qq_35423190/article/details/125451637