-
数据库的架构
Client、Parser(词法分析、语法分析、生成AST树)、Analyzer(变量绑定、类型推导、语义检查、安全、权限检查、完整性检查,为生成计划做准备)、Optimizer(为查询生成性能最优的执行计划、进行代价评估、Executor将执行计划翻译成可执行的物理计划、Storage engine)
-
设计数据库存储的要点
性能瓶颈在哪里:数据选择、数据读取、构造内存数据、计算
选择什么样的数据格式:是否可以并发处理、是否可以构建索引、行存、列存或者行列混合存储
选择什么样的索引:读写方式:读多写少、读少写多、点差场景、分析型场景
-
列存的优点:
数据压缩:
数据压缩可以使读的数据量更少,在IO密集型计算中获得大的性能优势
相同类型压缩效率更高
排序之后压缩效率更高
可以针对不同类型使用不同的压缩算法
常见压缩算法:LZ4、Run-length encoding、Delta encoding
数据处理:
查询优化:可以选择特定的列做计算而不是读所有列;对聚合计算友好
延迟物化:(物化:将列数据转换为可以被计算或输出的行数据或者内存数据结果的过程,物化后的数据通常可以用来做数据过滤,聚合计算,join);缓存友好;CPU/内存带宽友好;可以利用到执行计划和算子优化(例如filter);保留直接在压缩列做计算的机会
向量化:
SIMD(single instruction multiple data),对于现代多核CPU,其都有能力用一条指令执行多条数据
执行模型:数据需要按批读取,函数的调用需要明确数据类型;列存数据库适合设计出这样的执行模型,从而使用向量化技术
-
行存:
优点:数据被保存在一起;INSERT/UPDATE容易
缺点:选择(select)时即使只涉及某几列,所有数据也都会被读取
使用场景:点查询(返回记录少,基于索引的简单查询)
增删改操作较多
列存:
优点:查询时只有涉及到的列会被读取;投影(projection)很高效;任何列都能作为索引;便于做延迟物化和向量化计算;压缩效率高,每一列可以使用不同的压缩算法。
缺点:选择完成时,被选择的列要重新组装;INSERT/UPDATE比较麻烦;不适合点查询。
使用场景:统计分析类查询(OLAP,比如数据仓库业务,此类型的表上会做大量的汇聚计算,且涉及的列操作较少,关联、分组操作较多)
即时查询(查询条件不确定,行存表扫描难以使用索引)
-
ClickHouse索引设计:
主键索引
数据按照主键顺序一次排序(例子:UserID首先做排序,然后是URL,最后是EventTime)
数据被组织成granule(做数据处理的最小数据单位,引擎读数据时最少读取一个granule),方便构建稀疏索引,方便并行计算
每个granule对应primary.idx里面的一行
默认每8192行记录主键的一行值,primary.idx需要全部加载到内存
每个主键的一行数据称为一个mark
每列都有一个mark文件,存储所有granule在物理文件里的地址
mark文件里每行存储两个地址:block_offset、grabule_offset
-
索引的缺陷和优化:
缺陷:数据按照key的顺序做排序,因此只有第一个key的过滤效果好,后面的key过滤效果依赖第一个key的基数大小
二级索引:在url列上构建二级索引
构建多个主键索引:再建一个表(数据需要同步两份,查询需要用户判断查哪张表);建一个物化视图(数据自动同步到隐式,查询需要用户判断查哪张表);使用projection(数据自动同步到隐式表,查询路由到最优的表)
-
数据合并
一个part内的数据是有序的
不同part内的数据是无序的
数据合并是将多个part合并成一起的过程
part的合并发生在一个分区内
数据的可见性(数据合并过程中,未被合并的数据对查询可见;数据合并完成后,新part可见,被合并的part被标记删除)
-
数据查询
通过主键找到需要读的mark
切分marks,然后并发的调度reader
reader通过mark_block_offset得到需要读的数据文件的偏移量
reader通过mark_granule_offset得到解压后数据的偏移量
构建列式filter做数据过滤
-
典型使用场景:大宽表存储和查询、离线数据分析、实时数据分析、复杂类型查询