分布式计算细节

数据库的设计还是要结合业务模式来分析,例如OLTP通常采用航储存,OLAP通常采用列储存等等。

如果等值查询较多的话,那么按照哈希存储数据比较好;如果范围查询较多的话,那么按照有序数组储存数据比较好。

聚合操作:groupBy,count,sum等。

值得注意的一点是:尽管当前案例的分布式数据库支持sql语法,但底层存储依然采用nosql kv存储方式,因此有更多选择余地。

可采用lsm tree也可使用b-tree。

lsm-tree通过牺牲小部分读性能换来高性能写,使得LSM树成为非常流行的存储结构。

因为相比起提升写性能,提升读性能会容易很多,例如使用memcache等手段都可迅速提升读性能。

其技术思路有点类似Redis的AOF方式,先不考虑排序连续写入一个内存,然后满了后开新进程将内容排序合并到磁盘里。

lsm-tree结构分析:LSM-tree 基本原理及应用 - 简书

目录

一、如何将分布式计算表的数据映射到分布式KV存储引擎

三、分布式数据库如何更改schema

四、优化

五、执行


一、如何将分布式计算表的数据映射到分布式KV存储引擎

分布式系统里最核心的两个部分就是分布式计算和分布式存储。 

对于一个分布式数据库而言,一般将计算的sql layer置于分布式存储的kv之上,示意图如下:

分布式数据库一般同样支持sql语法,但实现逻辑大不一样。

mysql一般直接将数据按照行记录格式进行存储,每一个索引也是直接对应一个文件。而分布式数据库则会将每一个行记录或索引拆分出来,按照key-value的形式在kv存储引擎进行存储。

转换规则如下所示:

又或者这样:

    Key: tablePrefix_tableID_recordPrefixSep_rowID
    Value: [col1, col2, col3, col4]

其中 Key 的 tablePrefix/recordPrefixSep 都是特定的字符串常量,用于在 KV 空间内区分其他数据。 对于索引,会为每一个索引分配表内唯一的 indexID,然后按照如下规则编码成 Key-Value pair:

    Key: tablePrefix_tableID_indexPrefixSep_indexID_indexColumnsValue
    Value: rowID

当然,我们还需要考虑非唯一索引,这个时候上面的方法就行不通了,我们需要将 rowID 也编码进 Key 里使之成为唯一的:

   Key: tablePrefix_tableID_indexPrefixSep_indexID_ColumnsValue_rowID
   Value:null

二、如何通过parser将输入的sql语句转为易于执行的AST

AST:抽象语法树

参考链接:TiDB 源码阅读系列文章(五)TiDB SQL Parser 的实现 | PingCAP

转换实现示例:

// https://dev.mysql.com/doc/refman/8.0/en/join.html
JoinTable:
	/* Use %prec to evaluate production TableRef before cross join */
	TableRef CrossOpt TableRef %prec tableRefPriority
	{
		$$ = &ast.Join{Left: $1.(ast.ResultSetNode), Right: $3.(ast.ResultSetNode), Tp: ast.CrossJoin}
	}
	/* Your code here. */
	/*从左到右一次对应各个参数,具体含义可参考dml.go里的join结构体,此时5个参数*/
| TableRef CrossOpt TableRef "ON" Expression
  {
    on := &ast.OnCondition{Expr: $5}
    $$ = &ast.Join{Left: $1.(ast.ResultSetNode), Right: $3.(ast.ResultSetNode), Tp: ast.CrossJoin, On: on}
  }
  /*7个参数,各个参数的含义可参考下面的内容*/
| TableRef JoinType OuterOpt "JOIN" TableRef "ON" Expression
  {
    on := &ast.OnCondition{Expr: $7}
    $$ = &ast.Join{Left: $1.(ast.ResultSetNode), Right: $5.(ast.ResultSetNode), Tp: $2.(ast.JoinType), On: on}
  }

JoinType:
	"LEFT"
	{
		$$ = ast.LeftJoin
	}
|	"RIGHT"
	{
		$$ = ast.RightJoin
	}

OuterOpt:
	{}
|	"OUTER"

CrossOpt:
	"JOIN"
|	"INNER" "JOIN"

 使用lex逐项解析语法构造ast节点,然后对ast节点处理后顺序执行

三、分布式数据库如何更改schema

主要参考Google F1里面的schema变更算法。

核心是分为三个阶段,不一次从原始态直接跳到最终态,而是逐层转移,同时最多有两种模式存在,保证最终结果一致。

参考链接:builddatabase/schema-change.md at master · ngaut/builddatabase · GitHub

四、优化

优化过程主要分为逻辑优化(Logical Optimize)和物理优化(Physical Optimize)两个阶段。逻辑优化是将一棵逻辑算子树(LogicalPlan Tree)进行逻辑等价的变化,最后的结果是一棵更优的逻辑算子树;而物理优化则是将一棵逻辑算子树转换成一棵物理算子树(PhysicalPlan Tree)。这棵物理算子树就是我们说的物理执行计划,将交由 TiDB 执行引擎去完成后续的 SQL 执行过程。

逻辑优化阶段时,抽象语法树AST已经转换为对应的逻辑算子树,逻辑优化就是将一个逻辑算子树进行逻辑上等价变换的过程。逻辑优化基于规则实施,典型的规则包括:列剪裁、谓词下推、最大最小消除等。

物理优化是将逻辑算子树转换为物理算子树的过程。在物理优化中,优化器会结合数据的分布(统计信息)情况来对查询计划进行优化,物理优化是一个记忆化搜索的过程,搜索的目标是为逻辑执行计划寻找满足特定物理属性的物理执行计划,并在其中选择代价最低的作为搜索结果,因此也被称为基于代价的优化(Cost-Based Optimization,CBO),例如 DataSource 应该选择怎样的扫描路径(使用哪个索引),Join 应该选择怎样的执行方式(HashJoin、MergeJoin 或 IndexJoin)等。

因为分布式数据库里传递数据有很大的代价,因此在物理优化过程中还会尽量把操作流程下推到节点进行具体处理。

参考示例:

1-聚合查询

 此外,在join的过程中还涉及外连接以哪个表作为驱动表的问题和索引如何选取的问题,这些也都包含在物理优化过程中。要根据统计数据进行判断,决定如何执行操作。

五、执行

在数据库最后的执行阶段,需要尽可能加快执行效率。例如,通过多进程的方式进行join(每次检索部分数据)、不逐行读取而是采用向量化的方式按批读取等。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值