这是我参与「第五届青训营 」伴学笔记创作活动的第 16 天
一、本堂课重点内容
- 经典案例
- 发展历史
- 关键技术
- 企业实践
二、详细知识点介绍
经典案例
春节红包雨,一个发红包动作,包括两个操作:
- 从一个账户扣除钱
- 从另一个账户增加钱
UPDATE account_table SET balance = balance - 100 WHERE name = 'A';
UPDATE account_table SET balance = balance + 100 WHERE name = 'B';
RDBMS事务ACID
- 事务是由一组SQL语句组成的一个程序执行单元,它满足ACID特性。
BEGIN;
UPDATE account_table SET balance = balance - 100 WHERE name = 'A';
UPDATE account_table SET balance = balance + 100 WHERE name = 'B';
COMMIT;
红包雨与ACID
- Case1:A账户扣钱成功,B账户增钱失败,A账户余额减少100元,B账户余额不变。
这就要求事务的原子性,即要么全部成功,要么全部失败。
- Case2:A账户扣钱失败后余额不合法,B账户增钱成功,A账户余额非法,B账户余额增加100元。
这就要求事务的一致性,即事务执行前后,数据必须保持一致,从一个合法状态到另一个合法状态。
- Case3:A账户转100元给C账户,B账户也转100元给C账户,由于同时转帐,都是先获取C的余额,再加100,所以有可能会导致C只收到100元。
这就要求事务的隔离性,即多个事务并发执行时,一个事务的执行不应该影响其他事务的执行。
- Case4:A账户向B账户转100元,在A扣钱成功后,B增钱成功之前,服务器宕机,A账户扣钱成功,B账户增钱失败,A账户余额减少100元,B账户余额不变,数据没有持久化。
这就要求事务的持久性,即事务一旦提交,它对数据库中数据的改变就应该是永久性的。
红包雨与高并发
全国14亿人,假设有10亿人同时开始抢红包,每秒处理前一个请求,那么需要31年才能处理完。
因此,高并发是很重要的。
红包雨与高可靠
除夕晚上,大家正在抢红包,突然网络中断,这时候,如果没有高可靠,那么就会导致红包雨失败。
因此,高可靠是很重要的。
发展历史
前DBMS时代
人工管理
在现代计算机发明处理以前,通过人工的方式进行数据记录和管理:
- 结绳记事
- 清代钱庄账本
- 用于1890年人口普查的霍列瑞斯式打孔机
总结:效率低
文件系统
1950s,现代计算机的雏形基本出现。1956年IBM发布了第一个磁盘驱动器 -- Model 305 RAMAC,从此数据存储进入磁盘时代。在这个阶段,数据管理直接通过文件系统来实现。
总结:从写在纸上变成了写到文件里
DBMS时代
1960s,传统的文件系统已经无法满足数据管理的需求,也无法实现多用户共享数据和快速检索数据,于是出现了数据库管理系统(DBMS)。
DBMS:按照某种数据模型来组织、存储和管理数据的仓库。
所以通常按照数据模型的特定将传统数据库系统分为:
- 网状数据库
- 层次数据库
- 关系数据库
层次型、网状型和关系型数据库划分的原则是数据之间的联系方式。层次数据库是按记录来存取数据的;网状数据库是采用网状原理和方法来存储数据;关系型数据库是以行和列的形式存储数据。
DBMS数据模型
网状模型
网状数据库所基于的网状数据模型建立的数据之间的联系,能反映现实世界中信息的关联,是许多空间对象的自然表达形式。
1964年,世界上第一个数据库系统——集成数据存储(Integrated Data Storage,IDS)诞生于通用电气公司。IDS是世界上第一个网状数据库,奠定了数据库发展的基础,在当时得到了广泛的应用。在1970s网状数据库系统十分流行,在数据库系统产品中占据主导地位。
网状数据模型是以记录类型为结点的网络结构,即一个结点可以有一个或多个下级结点,也可以有一个或多个上级结点,两个结点之间甚至可以有多种联系,例如“教师”与“课程”两个记录类型,可以有“任课”和“辅导”两种联系,称之为复合链。
两个记录类型之间的值可以是多对多的联系,例如一门课程被多个学生修读,一个学生选修多门课程。
层次模型
1968年,世界上第一个层次数据库——信息管理系统(Information Management System,IMS)诞生于IBM公司。这也是世界上第一个大型商用的数据库系统。层次数据模型,即使用树形结构来描述实体及其之间关系的数据模型。
层次结构就是树结构,每棵树有且仅有一个根节点,其余节点都是非根节点。每个节点代表一个记录类型对应于实体的概念,记录类型的各个字段对应实体的各个属性,各个记录类型及其字段都必须记录。
关系模型
1970年,IBM的研究院E.F.Codd博士发表了一篇名为"A Relational Model of Data for Large Shared Data Banks"的论文,提出了关系模型的概念,奠定了关系模型的理论基础。1979年Oracle首次将关系模型商业化,后续DB2、SAP Sysbase ASE,and Informix等知名数据库产品也纷纷面世。
Course
| CourseId | CourseName | ...... |
|---|---|---|
| 1 | English | ...... |
| 2 | Computer | ...... |
| 3 | Maths | ...... |
Student
| StudentId | StudentName | ...... |
|---|---|---|
| 1 | Tom | ...... |
| 2 | Jerry | ...... |
| 3 | Spike | ...... |
| 4 | Tuffy | ...... |
CourseSelect
| RecordId | CourseId | StudentId |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 2 |
| 5 | 2 | 3 |
| 6 | 3 | 1 |
| 7 | 3 | 3 |
| 8 | 3 | 4 |
使用表格表示实体和实体之间关系的数据模型称之为关系数据模型。
关系数据模型中,无论是是实体、还是实体之间的联系都是被映射成统一的关系—一张二维表,在关系模型中,操作的对象和结果都是一张二维表,它由行和列组成;
关系型数据库可用于表示实体之间的多对多的关系,只是此时要借助第三个关系—表,来实现多对多的关系;
| 网状模型 | 层次模型 | 关系模型 | |
|---|---|---|---|
| 优势 | |||
| 劣势 |
SQL语言
-
高度非过程化
非关系数据模型的数据操纵语言是面向过程的语言,用其完成用户请求时,必须指定存取路径。而用SQL进行数据操作,用户只需提出“做什么”,而不必指明“怎么做”,因此用户无须了解存取路径,存取路径的选择以及SQL语句的操作过程由系统自动完成。这不但大大减轻了用户负担,而且有利于提高数据独立性。
-
面向集合的操作方式
SQL采用集合操作方式,不仅查找结果可以是元组的集合,而且一次插入、删除、更新操作的对象也可以是元组的集合。
-
语言简洁,易学易用
SQL功能极强,但由于设计巧妙,语言十分简洁,完成数据定义、数据操纵、数据控制的核心功能只用了9个动词: CREATE、 ALTER、DROP、 SELECT、 INSERT、 UPDATE、 DELETE、GRANT、 REVOKE。且SQL语言语法简单,接近英语口语,因此容易学习,也容易使用。
关键技术
一条SQL的一生
UPDATE account_table SET balance = balance - 100 WHERE user_id = 1;
SQL引擎
Parser
解析器(Parser)一般分为词法分析(Lexical Analysis)、语法分析(Syntax Analysis)、语义分析(Semantic Analysis)等步骤,其目的是将SQL语句转换成一棵语法树(Syntax Tree),以便后续的查询优化和执行。
所有的代码在执行之前,都存在一个解析编译的过程,差异点无非在于是静态解析编译还是动态的。SQL语言也类似,在SQL查询执行前的第一步就是查询解析。
- 词法分析:将一条SQL语句对应的字符串分割为一个个token,这些token可以简单分类。
- 语法分析:把词法分析的结果转为语法树。根据tocken序列匹配不同的语法规则,比如这里匹配的是update语法规则,类似的还有insert、delete、select、create、drop等等语法规则。根据语法规则匹配SQL语句中的关键字,最终输出一个结构化的数据结构。
- 语义分析:对语法树中的信息进行合法性校验。
UPDATE account_table SET balance = balance - 100 WHERE user_id = 1;
Optimizer
为什么要有优化器呢?因为SQL语句的执行效率和执行计划是有关系的,优化器的作用就是根据用户的SQL语句,生成最优的执行计划。
基于规则的优化RBO(Rule Based Optimization)
-
表连接优化
SELECT * FROM A, B, C WHERE A.a1 = B.b1 AND A.a1 = C.b1;这是一个三表连接查询,如果按照笛卡尔积的方式进行计算,那么需要执行的次数是A表的记录数乘以B表的记录数乘以C表的记录数,这个计算量是非常大的。而如果先对两个表进行连接,再对连接后的结果再进行一次连接,那么计算量就会大大减少。比如,A、B、C都有10条数据,先A与B连接,最多得到10条数据(计算100次),再将这10条数据与C表连接,最多得到10条数据(计算100次),总共计算了200次,而如果按照笛卡尔积的方式计算,需要计算1000次。那么此时该选择哪两个表先进行连接呢?这就需要优化器来决定了。一般会将小表进行连接之后再连接大表。这样可以最大减少前阶段生成的数据。
-
条件化简
a = 5 and b > a 化简得到 a = 5 and b > 5 a > 5 and a < b and b = 1 化简得到 false -
Scan优化
- 唯一索引
- 普通索引
- 全表扫描
基于代价的优化CBO(Cost Based Optimization)
一个查询语句可能有多种执行计划,优化器会根据代价来选择最优的执行计划。代价是指执行计划的执行时间,代价越小,说明执行计划越优。
Executor
火山模型
每个Operator调用Next操作,访问下层Operator,获得下层Operator返回的一行数据,经过计算之后,将这行数据返回给上层。
优点:
- 每个算子独立抽象实现,相互之间没有耦合,逻辑结构简单
缺点:
- 每计算一条数据有多次函数调用开销,导致CPU效率不高
改进方向:
- 向量化
- 编译执行
向量化
每个Operator每次计算的不再是一行数据,而是一批数据(Batch N行数据),计算完成后向上层算子返回一个Batch。
优点:
- 函数调用次数将为1/N
- CPU cache命中率更高
- 可以利用CPU提供的SIMD(Single Instruction Multiple Data)指令集,提高计算效率
向量化执行更适合于大批量数据处理,对于很多单行数据处理并没有优势,往往搭配列式存储使用。
编译执行
将所有的操作封装到一个函数里面,函数调用的代价也能大幅度降低。
用户SQL千变万化怎么办?难道要穷举所有的SQL,给每一个SQL都预先写好一个执行函数吗?
答:LLVM动态编译执行技术。
代码生成之后数据库运行时仍然是一个 for 循环,只不过这个循环内部的代码从简单的一个虚函数调用plan.next()展开成了一系列具体的运算逻辑,这样数据就不用再各个 operator 之间进行传递,而且有些数据还可以直接被存放在寄存器中,进一步提升系统性能。整个操作有点像inline 函数,把所有的操作inline到一个函数中去。
LLVM动态编译执行技术,根据优化器产生的计划,动态的生成执行代码。
存储引擎
InnoDB
Buffer Pool
- MySQL中每个chunk的大小一般为128M,每个block对应一个page,一个chunk下面有8192个block。这样可以避免内存碎片化。
- 分成多个instance,可以有效避免并发冲突。
- Page id % instance num得到它属于哪个instance
当buffer pool里的页面都被使用之后,再需要换存其他页面怎么办?
- 淘汰已有的页面
基于什么规则淘汰?
- 淘汰那个最近一段时间最少被访问过的缓存页了,这种思想就是典型的 LRU 算法了。
- 普通的LRU算法存在缺陷,考虑我们需要扫描100GB的表,而我们的buffer pool只有1GB,这样就会因为全表扫描的数据量大,需要淘汰的缓存页多,导致在淘汰的过程中,极有可能将需要频繁使用到的缓存页给淘汰了,而放进来的新数据却是使用频率很低的数据。
- MySQL 确实没有直接使用 LRU 算法,而是在 LRU 算法上进行了优化。
- MySQL 的优化思路就是:对数据进行冷热分离,将 LRU 链表分成两部分,一部分用来存放冷数据,也就是刚从磁盘读进来的数据,另一部分用来存放热点数据,也就是经常被访问到数据。
- 当从磁盘读取数据页后,会先将数据页存放到 LRU 链表冷数据区的头部,如果这些缓存页在 1 秒之后被访问,那么就将缓存页移动到热数据区的头部;如果是 1 秒之内被访问,则不会移动,缓存页仍然处于冷数据区中。
- 淘汰时,首先淘汰冷数据区。
Page
B+ Tree
事务引擎
Atomicity 与 Undo Log
如果事务中的某个操作失败了,那么这个事务中的所有操作都应该回滚。
如何将事务中的所有操作都回滚呢?
答:Undo Log
Undo Log是逻辑日志,记录的是数据的增量变化,它的作用是保证事务的原子性和事务并发控制。可以用于事务回滚,以及提供多版本机制(MVCC),解决读写冲突和一致性读的问题。其实记录的就是相反的操作。
Isolation 与 锁
- Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
- 如果多个并发事务访问同一行记录,就需要锁机制来保证了。
- 读写是否冲突?读写互不阻塞,MVCC机制。
Isolation 与 MVCC
MVCC的意义:
- 读写互不阻塞
- 降低死锁概率
- 实现一致性读
Undo Log在MVCC的作用:
- 每个事务有一个单增的事务ID
- 数据页的行记录中包含了DB_ROW_ID,DB_TRX_ID,DB_ROLL_PTR
- DB_ROLL_PTR将数据行的所有快照记录通过链表的结构串联了起来,这样就可以通过DB_ROLL_PTR找到该行记录的所有历史版本。
Durability 与 Redo Log
如何保证事务结束后,对数据的修改永久保存?
-
方案一:事务提交前页面写盘
有随机IO(数据比较分散),写放大(只需要写一部分数据,却要写一整个页)
-
方案二:WAL(Write-Ahead Logging)
redo log是物理日志,记录的是页面的变化,它的作用是保证事务持久化。如果数据写入磁盘前发生故障,重启MySQL后会根据redo log重做,也就是说不管发不发生故障,磁盘数据都是最终一致的。
- 优点
- 只记录增量变化,没有写放大
- Append Only,没有随机IO
- 优点
三、实践练习例子
本节课程没有给出相应的实践练习例子。
四、课后个人总结
本节课程从关系型数据库的发展历史开始,介绍了关系型数据库的基本概念,包括关系型数据库的基本特性、关系型数据库的基本组成、关系型数据库的基本架构、关系型数据库的基本原理等。解析了其关键技术点,包括存储引擎、事务引擎、锁、MVCC、WAL等。