ClickHouse | 青训营笔记

63 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天

索引设计

hashIndex

不适合做范围查询

image.png

B-tree

每个节点都保存数据

image.png

B+tree

中间节点只保存索引,不保存实际数据,这样中间节点可以保存更多的值,这样大数据量时可以尽量降低树的高度

clickhouse不使用常规索引原因

image.png

向b树都是排序树,大数据量写入,还要排序,速度没有直接写入磁盘快

Lsm Tree

image.png

SSTable

是磁盘存储的文件,在磁盘存储以segment为粒度存储,每个segment内部数据是有序的,不同segment里面无法保证有序性

image.png

segment唯一修改的方式就是重新写入一个新的segment然后把原先的segment删除掉

MemTable

如果大量数据写入就要频繁访问磁盘,效率就不高,因此先写入内存,当内存数据达到一定程度再批量写入磁盘

image.png

数据查询

image.png

合并

当segment数量太多时,索引的数量也就增加,为了提高数据访问效率因此会对segment进行合并,当合并后segment中有序数量也就会增多,可以提高压缩效率,也就保证读取更少的数据

image.png

具体实现
  1. 主键索引
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;
  1. 数据按照主键顺序一次排序

    1. UserID首先做排序,
    2. 然后是URL,
    3. 最后是EventTime

image.png

  1. 数据被组织成granule

    • granule是引擎做数据处理的最小数据单位,引擎读数据的时候不是按照一行一行读取的,而是最少读取一个granule
    • 方便构建稀疏索引
    • 方便并行计算

image.png

每个granule都对应primary.idx里面的一行

image.png

  1. 默认每8192行记录主键的一行值,primary.idx需要被全部加载到内存里面

含义就是每8192行建立一条索引,索引会全部加载到内存

image.png

  1. 每个主键的一行数据被称为一个mark

image.png

  1. 每个列都有这样一个mark文件,mark文件存储所有granule在物理文件里面的地址,每一列都有一个mark文件

  2. mark文件里面的每一行存储两个地址

    • 第一个地址称为block_offset,用于定位一个granule的压缩数据在物理文件中的位置,压缩数据会以一个block为单位解压到内存中。
    • 第二个地址称为granule_offset,用于定位一个granule在解压之后的block中的位置。

分两个地址原因是,granule只是我们的查询单元而不是最小写入单元,实际上可能多个granule压缩为一个block写入磁盘

索引的缺陷

  1. 缺陷:数据按照key的顺序做排序,因此只有第一个key的过滤效果好,后面的key过滤效果依赖第一个key的基数大小(假设第一个key大多数相同,那么第二个key则基本都是有序的,否则第一个key大多不同,也就意味着排序以第一个key为准,而第二个key无序,查询效果差)

image.png

索引的优化

尝试建立二级索引
  • 在URL列上构建二级索引

image.png

构建多个主键索引
  • 再建一个表(数据需要同步两份,查询需要用户判断查哪张表)

image.png

建物化视图

image.png

  • 建一个物化视图(数据自动同步到隐式表,查询需要用户判断查哪张表)

image.png

使用Projection
  • 使用Projection(数据自动同步到隐式表,查询自动路由到最优的表)

image.png

image.png

总结

  • 主键包含的数据顺序写入
  • 主键构造一个主键索引
  • 每个列构建一个稀疏索引
  • 通过mark的选择让主键索可以定位到每一列的索
  • 可以通过多种手段优化非主键列的索引

数据合并

合并后数据更大范围是有序的,查找时可以查找更少的part,提高压缩率,减少索引数量

image.png

  • 数据的可见性

    数据合并过程中,未被合并的数据对查询可见

    数据合并完成后,新part可见,被合并的part被标记删除

image.png

  • part的合并发生在一个分区内

image.png

此时可以看到1_6表示原先1-6的part进行了合并,合并次数为1次

数据查询

  1. 对于查询
SELECT
    URL,
    count(URL) AS Count
FROM hits_UserID_URL
WHERE UserID = 749927693
GROUP BY URL
ORDER BY Count DESC
LIMIT 10
  1. 通过主键找到需要读的mark
  2. 切分marks,然后并发的调度reader

image.png

  1. Reader 通过mark block_offset得到需要读的数据文件的偏移量
  2. Reader 通过mark granule_offset得到解压之后数据的偏移量

image.png

  1. 构建列式filter做数据过滤

image.png