这是我参与「第五届青训营」伴学笔记创作活动的第 14 天
关键技术实现
sql执行过程
一条SQL语句首先会经过Parser解析成AST,然后经过Optimizer解析成Plan表,之后经过Executor将结果存储到文件中,并记录到日志,返回结果。
SQl引擎
parse:
解析器(Parser)-般分为词法分析(L exical analysis)、语法分析(Syntax analysis)、语义分析(Semantic analyzer)等步骤。
所有的代码在执行之前,都存在一个解析编译的过程, 差 异点无非在于是静态解析编译还是动态的。SQL语言也类似,在SQL查询执行前的第一步就是查询解析。
词法分析:将-条SQL语句对应的字符串分割为一个个token, 这些token可以简单分类。 语法分析:把词法分析的结果转为语法树。根据tocken序列匹配不同的语法规则,比如这里匹配的是update语法规则,类似的还有insert、 delete、 select、create、 drop等等语法规则。根据语法规则匹配SQL语句中的关键字,最终输出一一个结构化的数据结构。 语义分析:对语法树中的信息进行合法性校验。
optimizer:优化器
优化器通常有两种策略,一种是基于规则优化,一种是基于代价优化
Executor:执行器
-
火山模型:
每个Operator调用Next操作,访问下层Operator,获得下层Operator返回的一行数据,经过计算之后,将这行数据返回给上层。
- 优点: 每个算子独立抽象实现,相互之间没有耦合,逻辑结构简单
- 缺点: 每计算一条数据有多次函数调用开销,导致CPU效率不高。
火山模型以Plan Tree为基础,调用关系是由根到叶,数据流是从叶到根
而针对火山模型的缺点也有以下两种优化方案:
-
向量化
每个Operator每次操作计算的不再是一行数据,而是一批数据(Batch N行数据),计算完成后向.上层算子返回一个Batch。 优点: 函数调用次数降低为1/N; CPU cache命中率更高; 可以利用CPU提供的SIMD(Single Instruction Multi Data)机制。
-
编译执行:
使用LLVM动态编译技术
代码生成之后数据库运行时仍然是个for循环,只不过这个循环内部的代码从简单的-一个虚函数调用plan.next0展开成了一系列具体的运算逻辑, 这样数据就不用再各个operator之间进行传递,而且有些数据还可以直接被存放在寄存器中,进一步提升系统性能,整个操作有点像inline函数,把所有的操作inline到一一个函数中去。
LLVM动态编译执行技术,根据优化器产生的计划,动态的生成执行代码。
存储引擎
Buffer Pool
位于内存,分为多个instance,instance分为多个chunk,chunk又有多个block组成,利用HashMap存储每个block
mysql在淘汰已有的页面时,采取改良LRU算法:
普通的LRU算法存在缺陷,考虑我们需要扫描100GB的表,而我们的buffer pool只有1GB,这样就会因为全表扫描的数据量大,需要淘汰的缓存页多,导致在淘汰的过程中,极有可能将需要频繁使用到的缓存页给淘汰了,而放进来的新数据却是使用频率很低的数据。 MySQL确实没有直接使用LRU算法,而是在LRU算法上进行了优化。 MySQL的优化思路就是:对数据进行冷热分离,将LRU链表分成两部分,一部分用来存放冷数据, 也就是刚从磁盘读进来的数据,另一部分用来存放热点数据, 也就是经常被访问到数据。 当从磁盘读取数据页后,会先将数据页存放到LRU链表冷数据区的头部,如果这些缓存页在1秒之后被访问,那么就将缓存页移动到热数据区的头部;如果是1秒之内被访问,则不会移动,缓存页仍然处于冷数据区中。 淘汰时,首先淘汰冷数据区。
Page
16k,磁盘上按page存储
innoDB存储引擎中,常见的页类型有:
- 数据页(B-tree Node)
- undo页(undo Log Page)
- 系统页 (System Page)
- 事物数据页 (Transaction System Page)
- 插入缓冲位图页(Insert Buffer Bitmap)
- 插入缓冲空闲列表页(Insert Buffer Free List)
- 未压缩的二进制大对象页(Uncompressed BLOB Page)
- 压缩的二进制大对象页 (compressed BLOB Page)
索引
使用B+树的数据结构
页面内: 页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。 从根到叶: 中间节点存储