关系数据库大概分为三大块:存储、事务和 SQL。
-
存储:相当于关系数据库的数据结构。
-
事务:相当于关系数据库的算法。
-
SQL:相当于关系数据库操作的描述语言,其实就是相当于接口。SQL 在数据库实现中相当于接口的实现。
存储在工业界基本上最常见的两种基础的数据结构:哈希表、B+ 树。
哈希表可以实现 O(1) 的这种计算,支持 put/get,哈希表 put/get 的性能非常快。唯一的缺陷是不支持范围 scan。
另外一种最常见的是 B+ 树,是今天关系数据库中用的最多的一种方式。B+ 树最大的优势它可以做范围 scan,并且它是一个平衡的二叉树,它的读、写、删除都非常的均衡,不会出现读性能特别好,写性能特别差或者读性能特别差,写性能特别好的情况,是一个非常均衡的算法。
最近一二十年出现了一个叫做 LSM-Tree 的基础数据结构。LSM-Tree 最大的特点就是它会更适合于写多读少的场景,其核心思想就是将离散的随机写请求都转换成批量的顺序写请求,大幅度减少随机写操作,提高写的性能。
数据库存储就是面向磁盘设计的一颗 B+ 树。
事务相当于关系数据库的算法,是整个数据库中最难的部分。事务中有四个核心属性:
-
原子性(Atomicity):事务操作要么全部成功,要么全部失败。即一旦事务出错,就回滚事务。
-
一致性(Consistency):一个事务只能使数据库从一个一致的状态跳到另一个一致的状态,不能存在一个中间的状态。
-
隔离性(Isolation):多个并发事务互相不影响,就如同多个事务串行执行一般。
-
持久性(Duration):事务一旦提交成功对数据库的影响就是永久的。
SQL 就相当于关系数据库的操作语言,它其实就是一层基于关系代数的接口,并且非常有理论基础。在数据库内部有专门一个模块(SQL 引擎)来实现这一套接口。
下表为 SQL 请求执行流程的步骤说明。
步骤 | 说明 |
---|---|
Parser(词法/语法解析模块) | 在收到用户发送的 SQL 请求串后,Parser 会做词法/语法分析,将字符串分成一个个的“单词”,并根据预先设定好的语法规则解析整个请求,将 SQL 请求字符串转换成带有语法结构信息的内存数据结构,称为语法树(Syntax Tree)。 |
Query result cache | 直接对 SQL 进行硬解析,若发现命中,将直接绕过下面所有过程,返回结果给应用方。 |
Resolver(语义解析模块) | Resolver 将生成的语法树转换为带有数据库语义信息的内部数据结构。在这一过程中,Resolver 将根据数据库元信息将 SQL 请求中的 Token 翻译成对应的对象(例如库、表、列、索引等),生成的数据结构叫做 Statement Tree。 |
Plan Cache(执行计划缓存模块) | 执行计划缓存模块会将该 SQL 第一次生成的执行计划缓存在内存中,后续的执行可以反复执行这个计划,避免了重复查询优化的过程。 Resolver 中生成的 Statement Tree 将会与该模块中的执行计划做匹配,若匹配命中,Plan Cache 直接将其物理执行计划发送到 Executor 中进行执行。 |
Transfomer(逻辑改写模块) | 分析用户 SQL 的语义,并根据内部的规则或代价模型,将用户 SQL 改写为与之等价的其他形式,并将其提供给后续的优化器做进一步的优化。Transformer 的工作方式是在原 Statement Tree 上做等价变换,变换的结果仍然是一棵 Statement Tree。 |
Optimizer(优化器) | 优化器是整个 SQL 请求优化的核心,其作用是为 SQL 请求生成最佳的执行计划。在优化过程中,优化器需要综合考虑 SQL 请求的语义、对象数据特征、对象物理分布等多方面因素,解决访问路径选择、联接顺序选择、联接算法选择、分布式计划生成等多个核心问题,最终选择一个对应该 SQL 的最佳执行计划。 |
Code Generator(代码生成器) | 将多个算子合并到一起,生成一个更高效的算子。 |
Executor(执行器) | 启动 SQL 的执行过程。
|
事务层和存储层在真实的工业界并没有上图中切分的那么干净,很多时候其实是相互掺杂,相互交错在一起。
事务管理器里面可以分为两个部分。
-
日志与恢复,所有的 SQL 在对磁盘进行操作的时候都会写一些日志,比如物理日志,逻辑日志,并且会提供一定的恢复功能,该部分负责事务的持久性。
-
并发控制,即怎么来控制对数据的事务,并发控制包括锁和 MVCC。并发控制负责保证事务的原子性和孤立性。
在存储层存在 Buffer Pool(缓冲池),所以不会直接从底层的磁盘中将数据提取出来。很多时候是通过 Buffer Pool 查询 Catalog,然后从 Catalog 中拿到所有的元数据信息,之后再发请求给存储管理器,由存储管理器最后发送到存储,然后从存储中将数据拿取出来。