68.【数据库】ClickHouse从入门到放弃-数据字典

929 阅读14分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情 |

文档参考:《ClickHouse原理解析与应用实践(数据库技术丛书)(朱凯)》

61.【数据库】ClickHouse从入门到放弃-概念场景 - 掘金 (juejin.cn)

64.【数据库】ClickHouse从入门到放弃-架构概述 - 掘金 (juejin.cn)

65.【数据库】ClickHouse从入门到放弃-update和delete的使用 - 掘金 (juejin.cn)

66.【数据库】ClickHouse从入门到放弃-数据类型转换 - 掘金 (juejin.cn)

67.【数据库】ClickHouse从入门到放弃-分区表 - 掘金 (juejin.cn)

1.数据字典

数据字典是ClickHouse提供的一种非常简单、实用的存储媒介,它以键值和属性映射的形式定义数据。字典中的数据会主动或者被动(数据是在ClickHouse启动时主动加载还是在首次查询时惰性加载由参数设置决定)加载到内存,并支持动态更新。由于字典数据常驻内存的特性,所以它非常适合保存常量或经常使用的维度表数据,以避免不必要的JOIN查询。

数据字典分为内置与扩展两种形式,顾名思义,内置字典是ClickHouse默认自带的字典,而外部扩展字典是用户通过自定义配置实现的字典。 在正常情况下,字典中的数据只能通过字典函数访问(ClickHouse特别设置了一类字典函数,专门用于字典数据的取用)。但是也有一种例外,那就是使用特殊的字典表引擎。在字典表引擎的帮助下,可以将数据字典挂载到一张代理的数据表下,从而实现数据表与字典数据的JOIN查询。

1.1 内置字典

ClickHouse目前只有一种内置字典——Yandex.Metrica字典。从名称上可以看出,这是用在ClickHouse自家产品上的字典,而它的设计意图是快速存取geo地理数据。但较为遗憾的是,由于版权原因Yandex并没有将geo地理数据开放出来。这意味着ClickHouse目前的内置字典,只是提供了字典的定义机制和取数函数,而没有内置任何现成的数据。所以内置字典的现状较为尴尬,需要遵照它的字典规范自行导入数据。

这两项配置是惰性加载的,只有当字典首次被查询的时候才会触发加载动作。填充Yandex.Metrica字典的geo地理数据由两组模型组成,可以分别理解为地区数据的主表及维度表。这两组模型的数据分别由上述两项配置指定,现在依次介绍它们的具体用法。 1.path_to_regions_hierarchy_file

path_to_regions_hierarchy_file等同于区域数据的主表,由1个regions_hierarchy.txt和多个regions_hierarchy_[name].txt区域层次的数据文件共同组成,缺一不可。其中[name]表示区域标识符,与i18n类似。这些TXT文件内的数据需要使用TabSeparated格式定义,其数据模型的格式如表5-1所示。

image.png 2.path_to_regions_names_files

path_to_regions_names_files等同于区域数据的维度表,记录了与区域ID对应的区域名称。维度数据使用6个regions_names_[name].txt文件保存,其中[name]表示区域标识符与regions_hierarchy_[name].txt对应,目前包括ru、en、ua、by、kz和tr。上述这些区域的数据文件必须全部定义,这是因为内置字典在初次加载时,会一次性加载上述6个区域标识的数据文件。如果缺少任何一个文件就会抛出异常并导致初始化失败。

这些TXT文件内的数据同样需要使用TabSeparated格式定义,其数据模型的格式如表5-2所示。

image.png

1.2 使用内置字典

在知晓了内置字典的开启方式和Yandex.Metrica字典的数据模型之后,就可以配置字典的数据并使用它们了。首先,在/opt路径下新建geo目录:

# mkdir /opt/geo

接着,进入本书附带的演示代码,找到数据字典目录。为了便于读者测试,事先已经准备好了一份测试数据,将下列用于测试的数据文件复制到刚才已经建好的/opt/geo目录下:

# pwd
/opt/geo
# ll
total 36
-rw-r--r--. 1 root root 3096 Jul  7 20:38 regions_hierarchy_ru.txt
-rw-r--r--. 1 root root 3096 Jul  7 20:38 regions_hierarchy.txt
-rw-r--r--. 1 root root 3957 Jul  7 19:44 regions_names_ar.txt
-rw-r--r--. 1 root root 3957 Jul  7 19:44 regions_names_by.txt
-rw-r--r--. 1 root root 3957 Jul  7 19:44 regions_names_en.txt
-rw-r--r--. 1 root root 3957 Jul  7 19:44 regions_names_kz.txt
-rw-r--r--. 1 root root 3957 Jul  7 19:44 regions_names_ru.txt
-rw-r--r--. 1 root root 3957 Jul  7 19:44 regions_names_tr.txt
-rw-r--r--. 1 root root 3957 Jul  7 19:44 regions_names_ua.txt

最后,找到config.xml并按照5.1.1节介绍的方法开启内置字典。 至此,内置字典就已经全部设置好了,执行下面的语句就能够访问字典中的数据:

SELECT regionToName(toUInt32(20009))
┌─regionToName(toUInt32(20009))─┐
│  Buenos Aires Province          │
└────────────────────┘

可以看到,对于Yandex.Metrica字典数据的访问,这里用到了regionToName函数。类似这样的函数还有很多,在ClickHouse中它们被称为Yandex.Metrica函数。关于这套函数的更多用法,请参阅官方手册。

2.外部扩展字典

2.1 准备字典数据

在接下来的篇幅中,会逐个介绍每种扩展字典的使用方法,包括它们的配置形式、数据结构及创建方法,但是在此之前还需要进行一些准备工作。为了便于演示,此处事先准备了三份测试数据,它们均使用CSV格式。其中,第一份是企业组织数据,它将用于flat、hashed、cache、complex_key_hashed和complex_key_cache字典的演示场景。这份数据拥有id、code和name三个字段,数据格式如下所示:

1,"a0001","研发部"
2,"a0002","产品部"
3,"a0003","数据部"
4,"a0004","测试部"
5,"a0005","运维部"
6,"a0006","规划部"
7,"a0007","市场部"

第二份是销售数据,它将用于range_hashed字典的演示场景。这份数据拥有id、start、end和price四个字段,数据格式如下所示: 最后一份是asn数据,它将用于演示ip_trie字典的场景。这份数据拥有ip、asn和country三个字段,数据格式如下所示:

"82.118.230.0/24","AS42831","GB"
"148.163.0.0/17","AS53755","US"
"178.93.0.0/18","AS6849","UA"
"200.69.95.0/24","AS262186","CO"
"154.9.160.0/20","AS174","US"

你可以从下面的地址获取到上述三份数据:

·github.com/nauu/clickh…

·github.com/nauu/clickh…

·github.com/nauu/clickh…
下载后,将数据文件上传到ClickHouse节点所在的服务器即可。

2.2 扩展字典配置文件的元素组成

扩展字典的配置文件由config.xml文件中的dictionaries_config配置项指定:

<!-- Configuration of external dictionaries. See:
         https://clickhouse.yandex/docs/en/dicts/external_dicts/
-->
    <dictionaries_config>*_dictionary.xml</dictionaries_config>

在默认的情况下,ClickHouse会自动识别并加载/etc/clickhouse-server目录下所有以_dictionary.xml结尾的配置文件。同时ClickHouse也能够动态感知到此目录下配置文件的各种变化,并支持不停机在线更新配置文件。

在单个字典配置文件内可以定义多个字典,其中每一个字典由一组dictionary元素定义。在dictionary元素之下又分为5个子元素,均为必填项,它们完整的配置结构如下所示:

<?xml version="1.0"?>
<dictionaries>
    <dictionary>
        <name>dict_name</name>
 
        <structure>
        <!—字典的数据结构 -->
        </structure>
 
        <layout>
        <!—在内存中的数据格式类型 -->
        </layout>
 
        <source>
        <!—数据源配置 -->
        </source>
 
        <lifetime>
        <!—字典的自动更新频率 -->
        </lifetime>
</dictionary>
</dictionaries>

在上述结构中,主要配置的含义如下。

·name:字典的名称,用于确定字典的唯一标识,必须全局唯一,多个字典之间不允许重复。 ·structure:字典的数据结构,2.3节会详细介绍。 ·layout:字典的类型,它决定了数据在内存中以何种结构组织和存储。目前扩展字典共拥有7种类型,2.4节会详细介绍。 ·source:字典的数据源,它决定了字典中数据从何处加载。目前扩展字典共拥有文件、数据库和其他三类数据来源,2.5节会详细介绍。 ·lifetime:字典的更新时间,扩展字典支持数据在线更新,2.6节会详细介绍。

2.3 扩展字典的数据结构

扩展字典的数据结构由structure元素定义,由键值key和属性attribute两部分组成,它们分别描述字典的数据标识和字段属性。structure的完整形式如下所示(在后面的查询过程中都会通过这些字段来访问字典中的数据):

<dictionary>
    <structure>
        <!— <id><key> -->
        <id> 
            <!—Key属性-->
        </id>
 
        <attribute>
            <!—字段属性-->
        </attribute>
        ...
    </structure>
</dictionary>

接下来具体介绍key和attribute的含义。

1.key key用于定义字典的键值,每个字典必须包含1个键值key字段,用于定位数据,类似数据库的表主键。键值key分为数值型和复合型两类。

(1)数值型:数值型key由UInt64整型定义,支持flat、hashed、range_hashed和cache类型的字典(扩展字典类型会在后面介绍),它的定义方法如下所示。

<structure>
    <id>
        <!—名称自定义-->
        <name>Id</name>
    </id>
省略…

(2)复合型:复合型key使用Tuple元组定义,可以由1到多个字段组成,类似数据库中的复合主键。它仅支持complex_key_hashed、complex_key_cache和ip_trie类型的字典。其定义方法如下所示。

<structure>
    <key>
        <attribute>
            <name>field1</name>
            <type>String</type>
        </attribute>
        <attribute>
            <name>field2</name>
            <type>UInt64</type>
        </attribute>
        省略…
    </key>
省略…

2.attribute attribute用于定义字典的属性字段,字典可以拥有1到多个属性字段。它的完整定义方法如下所示:

<structure>
    省略…
    <attribute>
        <name>Name</name>
        <type>DataType</type>
        <!—空字符串-->
        <null_value></null_value>
        <expression>generateUUIDv4()</expression>
        <hierarchical>true</hierarchical>
        <injective>true</injective>
        <is_object_id>true</is_object_id>
    </attribute>
    省略…
</structure>

在attribute元素下共有7个配置项,其中name、type和null_value为必填项。这些配置项的详细说明如表5-3所示。

image.png

2.4 扩展字典的类型

扩展字典的类型使用layout元素定义,目前共有7种类型。一个字典的类型,既决定了其数据在内存中的存储结构,也决定了该字典支持的key键类型。根据key键类型的不同,可以将它们划分为两类:一类是以flat、hashed、range_hashed和cache组成的单数值key类型,因为它们均使用单个数值型的id;另一类则是由complex_key_hashed、complex_key_cache和ip_trie组成的复合key类型。complex_key_hashed与complex_key_cache字典在功能方面与hashed和cache并无二致,只是单纯地将数值型key替换成了复合型key而已。

接下来会结合2.1节中已准备好的测试数据,逐一介绍7种字典的完整配置方法。通过这个过程,可以领略到不同类型字典的特点以及它们的使用方法。

1.flat

flat字典是所有类型中性能最高的字典类型,它只能使用UInt64数值型key。顾名思义,flat字典的数据在内存中使用数组结构保存,数组的初始大小为1024,上限为500 000,这意味着它最多只能保存500 000行数据。如果在创建字典时数据量超出其上限,那么字典会创建失败。代码清单2-1所示是通过手动创建的flat字典配置文件。

代码清单2-1 flat类型字典的配置文件test_flat_dictionary.xml

<?xml version="1.0"?>
<dictionaries>
   <dictionary>
       <name>test_flat_dict</name>
       <source>
               <!—准备好的测试数据-->
               <file>
                   <path>/chbase/data/dictionaries /organization.csv</path>
                   <format>CSV</format>
               </file>
           </source>
       <layout>
           <flat/>
       </layout>
       <!—与测试数据的结构对应-->
       <structure>
           <id>
               <name>id</name>
           </id>
           <attribute>
               <name>code</name>
               <type>String</type>
               <null_value></null_value>
           </attribute>
           <attribute>
               <name>name</name>
               <type>String</type>
               <null_value></null_value>
           </attribute>
       </structure>
        <lifetime>
           <min>300</min>
           <max>360</max>
       </lifetime>
   </dictionary>
</dictionaries>

在上述的配置中,source数据源是CSV格式的文件,structure数据结构与其对应。将配置文件复制到ClickHouse服务节点的/etc/clickhouse-server目录后,即完成了对该字典的创建过程。查验system.dictionaries系统表后,能够看到flat字典已经创建成功。

SELECT name, type, key, attribute.names, attribute.types FROM system.dictionaries
┌─name──────────┬─type─┬─key────┬─attribute.names─┐
│ test_flat_dict       │ Flat  │ UInt64    │ ['code','name']  │
└──────────────┴────┴───────┴───────────┘

2.hashed

hashed字典同样只能够使用UInt64数值型key,但与flat字典不同的是,hashed字典的数据在内存中通过散列结构保存,且没有存储上限的制约。代码清单2-2所示是仿照flat创建的hashed字典配置文件。

代码清单2-2 hashed类型字典的配置文件test_hashed_dictionary.xml

<?xml version="1.0"?>
<dictionaries>
    <dictionary>
        <name>test_hashed_dict</name>
        与flat字典配置相同,省略…
        <layout>
            <hashed/>
        </layout>
        省略…
    </dictionary>
</dictionaries>

3.range_hashed

range_hashed字典可以看作hashed字典的变种,它在原有功能的基础上增加了指定时间区间的特性,数据会以散列结构存储并按照时间排序。时间区间通过range_min和range_max元素指定,所指定的字段必须是Date或者DateTime类型。

现在仿照hashed字典的配置,创建一个名为test_range_hashed_dictionary.xml的配置文件,将layout改为range_hashed并增加range_min和range_max元素。它的完整配置如代码清单2-3所示。

代码清单2-3 range_hashed类型字典的配置文件test_range_hashed_dictionary.xml

<?xml version="1.0"?>
<dictionaries>
    <dictionary>
        <name>test_range_hashed_dict</name>
        <source>
                <file>
                <path>/chbase/data/dictionaries/sales.csv</path>
                <format>CSV</format>
                </file>
            </source>
        <layout>
            <range_hashed/>
        </layout>
        <structure>
            <id>
                <name>id</name>
            </id>
            <range_min>
                    <name>start</name>
                    </range_min>
 
                <range_max>
                    <name>end</name>
                </range_max>
 
            <attribute>
                <name>price</name>
                <type>Float32</type>
                <null_value></null_value>
            </attribute>      
        </structure>
         <lifetime>
            <min>300</min>
            <max>360</max>
        </lifetime>
    </dictionary>
</dictionaries>

在上述的配置中,使用了一份销售数据,数据中的start和end字段分别与range_min和range_max对应。将配置文件复制到ClickHouse服务节点的/etc/clickhouse-server目录后,即完成了对该字典的创建过程。查验system.dictionaries系统表后,能够看到range_hashed字典已经创建成功:

SELECT name, type, key, attribute.names, attribute.types FROM system.dictionaries
┌─name───────────┬─type─────┬─key───┬─attribute.names─┐
│ test_range_hashed_dict │ RangeHashed │ UInt64 │ ['price']        │
└──────────────┴────────┴─────┴───────────┘

4.cache

cache字典只能够使用UInt64数值型key,它的字典数据在内存中会通过固定长度的向量数组保存。定长的向量数组又称cells,它的数组长度由size_in_cells指定。而size_in_cells的取值大小必须是2的整数倍,如若不是,则会自动向上取为2的倍数的整数。

cache字典的取数逻辑与其他字典有所不同,它并不会一次性将所有数据载入内存。当从cache字典中获取数据的时候,它首先会在cells数组中检查该数据是否已被缓存。如果数据没有被缓存,它才会从源头加载数据并缓存到cells中。所以cache字典是性能最不稳定的字典,因为它的性能优劣完全取决于缓存的命中率(缓存命中率=命中次数/查询次数),如果无法做到99%或者更高的缓存命中率,则最好不要使用此类型。代码清单2-4所示是仿照hashed创建的cache字典配置文件。

代码清单2-4 cache类型字典的配置文件test_cache_dictionary.xml

<?xml version="1.0"?>
<dictionaries>
    <dictionary>
        <name>test_cache_dict</name>
        <source>
            <!—- 本地文件需要通过 executable形式 -->
            <executable>
                <command>cat /chbase/data/dictionaries/organization.csv</command>
                <format>CSV</format>
            </executable>
        </source>
        <layout>
            <cache>
                <!—- 缓存大小  -->
                <size_in_cells>10000</size_in_cells>
            </cache>
        </layout>
        省略…
    </dictionary>
</dictionaries>

在上述配置中,layout被声明为cache并将缓存大小size_in_cells设置为10000。关于cells的取值可以根据实际情况考虑,在内存宽裕的情况下设置成1000000000也是可行的。还有一点需要注意,如果cache字典使用本地文件作为数据源,则必须使用executable的形式设置。

5.complex_key_hashed

complex_key_hashed字典在功能方面与hashed字典完全相同,只是将单个数值型key替换成了复合型。代码清单2-5所示是仿照hashed字典进行配置后,将layout改为complex_key_hashed并替换key类型的示例。

代码清单2-5 complex_key_hashed类型字典的配置文件test_complex_key_hashed_dictionary.xml

<?xml version="1.0"?>
<dictionaries>
    <dictionary>
        <name>test_complex_key_hashed_dict</name>
        <!—-  与hashed字典配置相同,省略…-->
        <layout>
            <complex_key_hashed/>
        </layout>        
        <structure>
                <!—- 复合型key  -->
                <key>
                    <attribute>
                        <name>id</name>
                        <type>UInt64</type>
                     </attribute>
                    <attribute>
                        <name>code</name>
                        <type>String</type>
                    </attribute>
                </key>
         省略…
        </structure>
        省略…

将配置文件复制到ClickHouse服务节点的/etc/clickhouse-server目录后,即完成了对该字典的创建过程。

6.complex_key_cache

complex_key_cache字典同样与cache字典的特性完全相同,只是将单个数值型key替换成了复合型。现在仿照cache字典进行配置,将layout改为complex_key_cache并替换key类型,如代码清单2-6所示。 代码清单2-6complex_key_cache类型字典的配置文件test_complex_key_cache_dictionary.xml

<?xml version="1.0"?>
<dictionaries>
    <dictionary>
        <name>test_complex_key_cache_dict</name>
        <!—-与cache字典配置相同,省略…-->
        <layout>
            <complex_key_cache>
                <size_in_cells>10000</size_in_cells>
            </complex_key_cache>
        </layout>
        <structure>
                <!—- 复合型Key  -->
                <key>
                    <attribute>
                        <name>id</name>
                        <type>UInt64</type>
                     </attribute>
                    <attribute>
                        <name>code</name>
                        <type>String</type>
                    </attribute>
                </key>
        省略…
        </structure>
        省略…

将配置文件复制到ClickHouse服务节点的/etc/clickhouse-server目录后,即完成了对该字典的创建过程。

7.ip_trie

虽然同为复合型key的字典,但ip_trie字典却较为特殊,因为它只能指定单个String类型的字段,用于指代IP前缀。ip_trie字典的数据在内存中使用trie树结构保存,且专门用于IP前缀查询的场景,例如通过IP前缀查询对应的ASN信息。它的完整配置如代码清单2-7所示。

代码清单2-7 ip_trie类型字典的配置文件test_ip_trie_dictionary.xml

<?xml version="1.0"?>
<dictionaries>
    <dictionary>
        <name>test_ip_trie_dict</name>
        <source>
            <file>
                <path>/chbase/data/dictionaries/asn.csv</path>
                <format>CSV</format>
            </file>
        </source>
            <layout>
                <ip_trie/>
            </layout>
            <structure>
                <!—虽然是复合类型,但是只能设置单个String类型的字段 -->
                <key>
                    <attribute>
                        <name>prefix</name>
                        <type>String</type>
                    </attribute>
                </key>
            <attribute>
                <name>asn</name>
                <type>String</type>
                <null_value></null_value>
            </attribute>
            <attribute>
                <name>country</name>
                <type>String</type>
                <null_value></null_value>
            </attribute>
        </structure>
        省略…
    </dictionary>
</dictionaries>

通过上述介绍,读者已经知道了7种类型字典的创建方法。在这些字典中,flat、hashed和range_hashed依次拥有最高的性能,而cache性能最不稳定。最后再总结一下这些字典各自的特点,如表所示。

image.png

image.png