这是我参与「第五届⻘训营 」笔记创作活动的第18天。
今天的老师介绍了列存储的概念,以及介绍了字节内部使用的开源列存储产品ClickHouse。这篇文章是对课后习题的讲解。
- 列存和行存的差别是什么,使用场景有什么不同
- 列存是将数据表按照列排列存入内存中。
- 由于数据表中相同列数据类型相同,列存可以针对每一列类型做不同的压缩算法,压缩效率高
- 对于数据汇聚操作效率更高
- 行存将数据表中按照行排列存入内存中。
- 行存适合进行UPDATE/INSERT更新,因为数据表中一行的数据在内存中相邻,一次索引就可以进行更新。
- 列存是将数据表按照列排列存入内存中。
- 列存的优点有哪些
- 查询时只有涉及到的列会被读取。
- 投影(Projection)很高效。
- 任何列都能作为索引。
- 便于做延迟物化和向量化计算
- 压缩效率高,每一列可以使用不同的压缩算法
- 列存的缺点有哪些
- 选择完成时,被选择的列要重新组装。
- INSERT/UPDATE比较麻烦。
- 点查询不适合。
- 列存适合什么样的索引
- 统计分析类查询(OLAP,比如数据仓库业务,此类型的表上会做大量的汇聚计算,且涉及的列操作较少,关联、分组操作较多)。
- 即时查询(查询条件不确定,行存表扫描难以使用索引)
- ClickHouse的列存是什么样的存储架构
- 对于单个表,结构如下
CREATE TABLE test.test_insert_local ( `p_date` Date, `id` Int32 ) ENGINE = MergeTree PARTITION BY p_date ORDER BY id SETTINGS index_granularity = 8192- ENGINE表示使用索引引擎
- partition是此表的逻辑结构,每个partition对应物理文件夹名字为part
├── 20220101_1_1_0 │ ├── checksums.txt │ ├── columns.txt │ ├── count.txt │ ├── data.bin │ ├── data.mrk3 │ ├── default_compression_codec.txt │ ├── minmax_p_date.idx │ ├── partition.dat │ ├── primary.idx │ └── versions.txt ├── 20220102_2_2_0 │ ├── checksums.txt │ ├── columns.txt │ ├── count.txt │ ├── data.bin │ ├── data.mrk3 │ ├── default_compression_codec.txt │ ├── minmax_p_date.idx │ ├── partition.dat │ ├── primary.idx │ └── versions.txt ├── detached └── format_version.txt- 对于每个part文件夹,都有一个主键索引
- 每个column都是一个文件,在自己的part文件夹下
- 每个column都有列索引
- ClickHouse的索引是怎么设计的
- 以此表为例子
数据按照主键顺序依次排序 UserID首先做排序,然后是URL,最后是EventTimeCREATE 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;- 数据被组织成granule,引擎处理的最小单位,每个granule都对应primary.idx中的一行
- primary.idx会记录每个granule中的一行,来作为稀疏索引,通常为每个granule第一行,最后一个是最后一行。
- 每个列都有这样一个mark文件,mark文件存储所有granule在物理文件里面的地址,每一列都有一个mark文件,mark文件里面的每一行存储两个地址
- 第一个地址称为block_offset,用于定位一个granule的压缩数据在物理文件中的位置,压缩数据会以一个block为单位解压到内存中。
- 第二个地址称为granule_offset,用于定位一个granule在解压之后的block中的位置。
- ClickHouse的查询是怎么使用索引的
- 通过主键找到需要读的mark
- 切分marks,然后并发的调度reader(因为一次读取多列,组成一行数据)
- Reader 通过mark block_offset得到需要读的数据文件的偏移量
- Reader 通过mark granule_offset得到解压之后数据的偏移量
- 构建列式filter做数据过滤(多个字段合并一行数据)