这是我参与「第五届青训营 」笔记创作活动的第15天
一、本文内容:
-
数据库基本概念
-
列式存储
-
ClickHouse存储设计
-
ClickHouse典型应用场景
二、详细介绍
ClickHouse的存储设计
ClickHouse的架构
- 架构图
- 表定义和结构
- 集群架构
ClickHouse的存储架构
- 数据结构
a.文件组织
b.文件内容
c. part和partition
- part是物理文件夹的名字
- partition是逻辑结构
d. part和column
- 每个column都是一个文件
- 所有的column文件都在自己的part文件夹下
e. column和index
- 一个part有一个主键索引
- 每个column都有列索引
索引设计
- 主键索引
CREATE TABLE hits_UserID_URL
(
`UserID` UInt32,
`URL` String,
`EventTime` DateTime
)
ENGINE = MergeTree
PRIMARY KEY (UserID, URL)
ORDER BY (UserID, URL, EventTime)
SETTINGS index_granularity = 8192, index_granularity_bytes = 0;
- 数据按照主键顺序依次排序 UserID首先做排序,然后是URL,最后是EventTime
- 数据被组织成granule
- granule是引擎做数据处理的最小数据单位,引擎读数据的时候不是按照一行一行读取的,而是最少读取一个granule
- 方便构建稀疏索引
- 方便并行计算
- 每个granule都对应primary.idx里面的一行
- 默认每8192行记录主键的一行值,primary.idx需要被全部加载到内存里面
- 每个主键的一行数据被称为一个mark
- 每个列都有这样一个mark文件,mark文件存储所有granule在物理文件里面的地址,每一列都有一个mark文件
- mark文件里面的每一行存储两个地址
- 第一个地址称为block_offset,用于定位一个granule的压缩数据在物理文件中的位置,压缩数据会以一个block为单位解压到内存中。
- 第二个地址称为granule_offset,用于定位一个granule在解压之后的block中的位置。
索引的缺陷和优化
- 缺陷:数据按照key的顺序做排序,因此只有第一个key的过滤效果好,后面的key过滤效果依赖第一个key的基数大小
- 二级索引
- 在URL列上构建二级索引
- 构建多个主键索引
- 再建一个表(数据需要同步两份,查询需要用户判断查哪张表)
- 建一个物化视图(数据自动同步到隐式表,查询需要用户判断查哪张表)
- 使用Projection(数据自动同步到隐式表,查询自动路由到最优的表)
数据合并
- 一个part内的数据是有序的
- 不同part之间的数据是无序的
- 数据合并是将多个part合并成一起的过程
- part的合并发生在一个分区内
- 数据的可见性
- 数据合并过程中,未被合并的数据对查询可见
- 数据合并完成后,新part可见,被合并的part被标记删除
数据查询
- 对于查询
SELECT
URL,
count(URL) AS Count
FROM hits_UserID_URL
WHERE UserID = 749927693GROUP BY URL
ORDER BY Count DESC
LIMIT 10
- 通过主键找到需要读的mark
- 切分marks,然后并发的调度reader
- Reader 通过mark block_offset得到需要读的数据文件的偏移量
- Reader 通过mark granule_offset得到解压之后数据的偏移量
- 构建列式filter做数据过滤
本文如有不足之处,欢迎大家指出