ClickHouse|青训营笔记
这是我参与【第五届青训营】伴学笔记创作活动的第16天。
一、重点内容
- 数据库分类、查询方式
- 行存、列存数据库
- Clickhouse的基础、底层实现
- Clickhouse的应用
二、详细知识点
1. 数据库基本概念
- 数据库概念:结构化信息/数据的有序集合,以电子形式存储在系统中
- 结构化信息:数据解析整理成有序集合
- 可以通过查询语言获取想要的信息
1.1 数据库类型
-
数据库的类型
- 关系型数据库:把数据以表方式储存,在各个表之间建立关系,通过表之间的关系操作不同表之间的数据
- 非关系数据库:NoSQL或非关系数据库支持存储和操作半结构化及半结构化数据
- 相较于关系型数据库无固定表结构
- 无表表之间的关系
- 数据间互相独立
-
部署方式
- 单机数据库:在一台计算机上完成数据查询存储的数据库
- 分布式数据库:位于不同站点的两个以上文件组成,数据库可以存储在多台计算机上,位于同一个物理位置或分散在不同的网络上
-
应用类型
- OTAP数据库:高速分析数据库,为多用户大量事务设计
- OLAP:同时分析多个数据维度,更好理解数据间的复杂关系
1.2 OLAP数据库
- 大量数据读写,PB级存储
- 多维分析,复杂聚合函数
- 窗口函数,自定义UDF
- 离线、实时分析
1.3 SQL
SELECT FROM WHERE GROUPBY HAVING ORDERBY LIMIT
- 定义数据类型
- 查询数据库数据
- 优点
- 标准化,ISO和ANSI长期建立SQL数据库标准
- 高度非过程化,用SQL进行数据操作,用户无需理解操作方式只需提出做什么即可
- 以同一语法结构提供两种使用方式
- 可以直接进行查询
- 可以嵌入其他语言中
- 容易使用,容易学习与传播
1.4 数据库架构
- client:解析SQL传递query
- parser/optimizer/analyzer/executor
- 进入存储引擎
- index manager
- transaction manager
- file manager
- cache manager
1.5 SQL执行
- Parser:词法分析、语法分析、生成AST树
- Analyzer:变量绑定,类型推导、语意检查、安全权限检查、完整性检查
- Optimizer:为查询生成性能最优的执行计划,进行代价评估
- Executor:将执行计划翻译成可执行的物理计划并驱动执行
1.6 存储引擎
- 如何存储
- 并发处理
- 构建索引
- 行列存
- 读写数据需求不同
- 读多写少
- 读少写多
- 点查场景
- 分析性场景
2. 列存储
2.1 列存储的优点
- 数据压缩可以使读的数据量更少,在IO密集型计算中有更大的性能优势
- 相同类型压缩效率更高
- 排序后压缩效率更高
- 可以针对不同类型使用不同算法
- LZ4:重复项越多越长压缩率更高
- Run-length encoding:计算有多少个字符
- delta encoding:数据存储为之前数据的差异
- 直接在压缩数据计算,无需解压
- 数据选择
- 可以选择特定列计算而不需读所有列
- 对聚合计算友好
- 延迟物化:尽可能推迟物化操作发生
- 缓存友好
- CPU/内存带宽友好
- 可以利用到执行计划和算子优化
- 保留直接在压缩列做计算的机会
- 向量化
- SIMD:现代多核CPU有能力一条指令执行多条数据
- 数据格式要求:处理多个数据,数据需要连续,需要明确数据类型
- 列存数据库可以使用向量化技术
- 按列读取
- 每种列类型定义数据读写逻辑
- 函数按列类型处理
2.2 行列存的比较
3. ClickHouse存储设计
3.1 表定义和结构
- 分布式表:不存储数据,用于将查询路由到集群的各个节点
- cluster:逻辑集群,由多个节点组成
- shard_key:指导数据写入分布式表时到分布方式
- 本地表:实际存储数据的表
- 结构
- 集群选择 on CLUSTER
- 引擎选择 engine=MergeTree()
- 数据排列方式 ORDER BY
- 数据如何在单节点分布 PARTITION BY
3.2 存储架构
-
存储文件
- part和column
- column是一个文件
- 所有column文件都在自己part文件夹下
- column和index
- 一个part有一个主key索引
- 每个column都有列索引
- part和column
-
索引设计
- hash index
- 将输入key通过hash function映射到一组bucket上
- 每个bucket包含一条指向一条记录的地址
- 只适用于等值比较
- B树
- 数据写入有序,支持增删改查
- 每节点有多个子节点
- 每节点按照升序排列key
- 每个key有指向左右子节点的引用
- B+树
- 所有数据都存储在叶子节点
- 叶子节点维护到相邻叶子节点的引用
- 通过key做二分查找,也可以通过叶子节点顺序访问
- hash index
-
问题
- 对于大数据量B树深度太高
- 索引数据量太大,多列如何平衡查询存储
- OLAP场景写入量非常大如何优化写入
-
LSM tree为大吞吐写入而生
- 着重优化顺序写入
- 主要数据结构
- SSTables
- key按顺序存储到文件中,称为segment
- 包含多个segment
- 每个segment写入磁盘后不可更改
- 新加入的数据智能生成新的segment
- Memtable
- 将内存中的数据保存在memtable中,大多数是binary search tree
- 当memtable存储的数据到达一定阈值时会按顺序写入磁盘
- SSTables
- 数据查询
- 需要从最新的segment开始遍历每个key
- 也可以为每个segment建立索引
- 合并(compaction)
- 将多个segments合并成一个
- 一般由后台线程完成
- 不同segmens写入新的segment时需要排序
- 形成新segment后旧segment会被删除
-
数据被划分为granules
- granules是最小的读取单元
- 不同之间可以平行读取
- 每个granule对应primary.idx里一行
- 每列会有一个mark文件保存每个granules地址
- 缺陷:因数据按key排列,只有第一个key的过滤效果好,后面的key过滤效果依据第一个key的基数大小
-
查询优化
- secondary index:URL构建二级索引
- 构建多个主key索引
- 在建一个表
- 建一个物化视图
- 数据自动同步到隐式表
- 查询需要用户判断查什么表
- 使用projection
- 类似物化视图,但不将数据写入新表
- 查询自动路由到最优表
-
数据合并
- part内数据有序
- 不同part内数据无序
- 数据合并将多个part合并一起
-
数据可见性
- 数据合并过程中未被合并的数据对查询可见
- 数据合并完成后新part可见,被合并的part被标记删除
-
数据查询
- 寻找对应列marks
- 切分marks并发调度reader
- reader通过mark offset得到需要读到文件偏移量
- reader通过mark granule_offset得到解压后偏移量
- 构建列式filter做数据过滤
4. 典型应用场景
4.1 大宽表存储查询
- 可以建非常多的列
- 增加删除清空列数据非常困难
- 查询时候引擎可以快速选择需要的列
- 可以将涉及到的过滤条件下推到存储引擎
- 动态表结构
- map中每个key都是一列
- map中每列都能单独查询
- 使用方式同普通列,可以做任何计算
4.2 离线数据分析
- 数据导入
- 数据通过spark生成clickhouse格式文件
- 导入hdfs通过hive2ch导入
- 数据直接导入到各个物理节点
- 数据按列倒入
- 保证查询可以及时访问已有数据
- 可以按需加载需要的列
4.3 在线数据分析
- 使用memorytable减少parts数量
- 数据先缓存在内存中
- 到达阈值后写入磁盘
4.4 复杂类型查询
- bitmap索引创建(某值存在哪些行)
- 读取时无需真正读取元文档
- bitmap64类型
- 压缩数据,group by bit
- lowcardinality
- 对低基数列使用字典编码
- 减少数据存储读写IO使用
- 可以做运行时压缩数据过滤
三、课程总结
今天的课程主要介绍了数据库和Clickhouse的相关知识。列存数据库是在日常学习中应用较少的一类,可能需要结合实际进行进一步的学习。