这是我参与「第五届青训营 」伴学笔记创作活动的第 27 天
本篇文章归档于 “第五届字节跳动青训营”,主要是为了完成和记录掘金的 “伴学笔记创作活动” 活动,如果你对我的其他文章感兴趣,可以去我的 专栏 中逛逛看有没有你想要的东西。
- 第 1 篇 - Kitex 口水话
- 第 2 篇 - Hertz 口水话
- 第 3 篇 - 微服务口水话
- 第 4 篇 - Kafka 口水话
- 第 5 篇 - BMQ 口水话
- 第 6 篇 - RecketMQ 口水话
- 第 7 篇 - 数据库口水话
- 第 8 篇 - RDBMS 口水话
- 第 9 篇 - TOS 口水话
- 第 10 篇 - tinyTikTok 环境配置
- 第 11 篇 - tinyTikTok 规范设计
- 第 12 篇 - tinyTikTok 项目管理
- 第 13 篇 - tinyTikTok 认证授权
- 第 14 篇 - tinyTikTok 服务功能
- 第 15 篇 - tinyTikTok 测试分析
- 第 16 篇 - tinyTikTok 项目总结
RDBMS 的发展
前 DBMS 时代:人工方式进行记录和管理 -> 文件系统进行记录和管理。
DBMS 时代:
- 网状模型:直接描述现实世界,存取效率高,但结构复杂,用户不易使用,访问程序设计复杂;
- 层次模型:结构简单,查询高效,可提供较好的完整性支撑,但无法表示 M:N 模型,插入、删除限制多,遍历子节点一定经过父节点,访问程序设计复杂;
- 关系模型:实体之间用二维表结构表示,方便表示 M:N 关系,数据访问路径对用户透明,但关联查询效率不够高,关系必须规范化。
SQL(Structured Query Language)的特点:
- 语法风格接近自然语言;且SQL语言语法简单,接近英语口语,因此容易学习,也容易使用;
- 高度非过程化:关系数据模型的数据操纵语言是面向意图的语言,用 SQL 进行数据操作,用户只需提出”做什么”,而不必指明”怎么做”,这不但大大减轻了用户负担,而且有利于提高数据独立性;
- 面向集合的操作方式:SQL采用集合操作方式,不仅查找结果可以是元组的集合,而且一次插入、删除、更新操作的对象也可以是元组的集合;
- 语言简洁,易学易用:SQL功能极强,但由于设计巧妙,语言十分简洁,完成数据定义、数据操纵、数据控制的核心功能只用了9个动词: CREATE、ALTER、DROP、SELECT、INSERT、UPDATE、 DELETE、GRANT、REVOKE。
RDBMS 的关键技术
一条 SQL 的生命周期:
- SQL 引擎
- 查询解析:将文本解析成结构化数据,也就是抽象语法树(AST,Abstract Syntax Tree);
- 查询优化:根据 AST 优化产生最优执行计划(Plan Tree);
- 查询执行:根据查询计划,完成数据读取、处理、写入等操作;
- 事务引擎:处理事务一致性、并发、读写隔离等;
- 存储引擎:内存中的数据缓存区、数据文件、日志文件。
SQL 引擎
所有的代码在执行之前,都存在一个解析编译的过程,差异点无非在于是静态解析编译还是动态的。解析器(Parser)通常包含以下步骤:
- 词法分析:将一条 SQL 语句对应的字符串分割为一个个 token,这些 token 可以简单分类;
- 语法分析:把词法分析的结果转为语法树。根据 token 序列匹配不同的语法规则,匹配 SQL 语句中的关键字,最终输出一个结构化的数据结构;
- 语义分析:对语法树中的信息进行合法性校验。
那对于数据库而言,一条 SQL 执行是需要代价的,对于用户而言,最直观的就是查询时间长短,但是在并发的情况下,就得考虑资源消耗了,因为一个用户的查询占用的资源多了,其他用户的资源就少了。因此,需要优化器(Optimizer)基于代价在不同的优化策略中做选择。
关于 SQL 的执行器,课件中提到了 3 种不同的模型:
- 火山模型:每个 Operator 调用 Next 操作,访问下层 Operator;获得下层 Operator 返回的一行数据,经过计算之后,将这行数据返回给上层。
- 优点:每个算子独立抽象实现,相互之间没有耦合,逻辑结构简单;
- 缺点:每计算一条数据有 多次函数调用开销,导致CPU效率不高;
- 向量化:每个 Operator 每次操作计算的不再是一行数据,而是 Batch(一批数据,N 行数据),计算完成后,向上层算子返回一个 Batch。
- 优点:函数调用次数降低为1/N,CPU cache命中率更高,可以利用CPU提供的SIMD(Single Instruction Multi Data)机制;
- 编译执行:代码生成之后数据库运行时仍然是一个 for 循环,只不过这个循环内部的代码从简单的一个虚函数调用 plan.next() 展开成了一系列具体的运算逻辑,这样数据就不用在各个 Operator 之间进行传递,而且有些数据还可以直接被存放在寄存器中,提升性能。
- 改进:还可以利用 LLVM 动态编译执行技术,根据优化器产生的计划,动态的生成执行代码。