这是我参与「第五届青训营」伴学笔记创作活动的第16天。今天的内容是关于关系型数据库 RDBMS 的。
1 经典案例
1.1 红包雨
假设 Y 老师得到1个亿。
数据库的2步操作:抖音红包池减少1个亿,Y 老师账号增加1个亿。
1.2 RDBMS 中的 ACID
事务 Transaction:由一组 SQL 语句组成的一个程序执行单元,需要满足 ACID 特性。
ACID:原子性、一致性、隔离性、持久性
1.3 红包雨与 ACID
-
原子性 Atomicity:两个操作要么同时成功,要么同时失败
-
一致性 Consistency:每个操作都必须是合法的,账户信息应该从一个有效的状态变为另一个有效的状态
-
隔离性 Isolation:两个操作在对同一个账号并发进行操作时,应该是相互不影响的,表现得像是串行操作
-
持久性 Duration:操作更新成功后,更新的结果应该永久性地保存下来,不会因为宕机等问题而丢失
-
高并发 Concurrency:同时 IO 操作
-
高可靠 High reliability/Availability:服务在关键时间/节点要稳定可靠
2 发展历史
2.1 前 DBMS 时代
人工管理:结绳记事 -> 账本记事 -> 打孔机
文件系统:通过磁盘的文件系统记录(类 txt)
DBMS:按照某种数据模型来组织、存储、管理数据的仓库
2.2 DBMS 数据模型
网状模型:用有向图表示实体和实体之间的联系的数据结构模型称为网状数据模型
层次模型:层次数据模型是用树状<层次>结构来组织数据的数据模型
关系模型:使用表格表示实体和实体之间关系的数据模型称之为关系数据模型
模型 | 优点 | 缺点 |
---|---|---|
网状模型 | 直接描述现实世界;存取效率较高 | 结构复杂;用户不易使用;访问程序设计复杂 |
层次模型 | 结构简单;查询效率高;可以提供较好的完整性支持 | 无法表示M:N的关系;插入、删除限制多;遍历子节点必须经过父节点;访问程序设计复杂 |
关系模型 | 实体及实体间的的联系都通过二维表结构表示;可以方便的表示M:N关系;数据访问路径对用户透明 | 关联查询效率不够高;关系必须规范化 |
2.3 SQL 语言
SQL = Structured Query Language 结构化查询语言
- 语法结构接近自然语言
- 高度非过程化(过程化如Python)
- 面向集合的操作方式
- 语言简洁,易学易用
完成数据定义、数据操纵、数据控制的核心功能只用了9个动词:CREATE, ALTER, DROP, SELECT, INSERT, UPDATE, DELETE, GRANT, REVOKE。
3 关键技术
3.1 一条 SQL 的一生
- AST 语法树
- Parser 解析器
- Optimizer 优化器
- Executor 执行器
3.2 SQL 引擎
Parser 解析器
解析器一般分为词法分析 lexical analyzer、语法分析 syntex analyzer、语义分析 semantic analyszer 等几个步骤。
Optimizer 优化器
优化器用来选择一个更好的方法。例如 A、B、C 三个表的连接,optimizer 判断按照怎样的顺序连接效果最好。
常见的优化思路:基于规则(Rule Base Optimizer, RBO)、基于代价(Cost Base Optimizer, CBO)
表连接优化:小表先连接
Scan 优化:唯一索引、普通索引、全表扫描
数据库索引:是数据库管理系统中辅助数据结构,用来协助快速查询、更新数据库表中数据。目前数据库中最常见的索引是通过 B+ 树实现的。
Executer 执行器
火山模型(因为图长得像火山喷发)
每个 Operator 算子调用 Next 操作访问下方 Operator,获得下方 Operator 返回的一行数据,经过计算之后,将这行数据返回给上层。
- 优点:每个算子独立实现,相互间没有耦合,逻辑结构简单
- 缺点:每计算一条数据有多次函数调用开销,CPU 利用率低
向量化计算模型
每个 Operator 每次操作计算的是一批数据 batch (很多行),计算完向上返回整个 banch。
- 优点:函数调用次数减少,CPU cache 命中率更高,可以利用 CPU 提供的 SIMD (Single Instruction Multi Data)机制
编译执行
将所有操作封装到一个函数里,函数调用的代价大幅度降低。
LLVM 动态编译技术用来解决用户可能写出的复杂 SQL。
3.3 存储引擎
以 InnoDB 为例。
在内存态做数据缓存,在磁盘上存储数据元信息、真实数据、事务日志。
Buffer Pool
在 MySQL 中,每个 chunk 的大学一般为 128 M,包含8192个 block。每个 block 对应一个 page。多个 chunk 称为1个 instance。
基于 LRU 算法(的优化,5/8和3/8)进行 buffer pool 的管理。
Page
大小为 16 K。
B+ 树
页面内:页面内使用二分法快速定位到对应的槽,再遍历该槽对应分组中的记录
从根到叶:中间节点存储
3.4 事务引擎
Atomicity 和 Undo Log
Undo Log 是逻辑日志,记录数据的增量变化。利用 Undo Log 可以进行事务回滚,从而保证原子性。除此之外还解决了多版本并发控制 MVCC,解决读写冲突和一致性读问题。
一致性通常由业务实现
Isolation 和锁
读的时候加 share lock,写的时候加 exclusive lock。在读读和写写时没有问题,但在读写冲突时会涉及到 MVCC 问题。
MVCC 的意义:读写互不阻塞、降低死锁概率、实现一致性读
Durability 和 Redo Log
- 如何保证对数据的修改永久保存?
- 方法1:事务提交前写入磁盘(缺点:随机 IO,写放大)
- 方法2:WAL Write-ahead log
Redo Log 是物理日志,记录的是页面的变化。作用是保证事务持久化。如果数据写入磁盘前发生故障,重启 MySQL 后会根据 Redo Log 重做。
4 企业实践
遇到的挑战:流量大(每秒百万量级)、突增流量、稳定性
大流量的解决:sharding,即分库分表。对业务数据进行水平拆分,通过代理层进行分片路由。
突增流量的解决:扩容(扩容 DB 物理节点的数量,利用影子表进行压测)、代理连接池(业务侧预热连接池、代理侧预热连接池、代理侧支持连接队列)
稳定性和可靠性的实现:3AZ 高可用(三机房部署,机房容灾、机房流量调度)、proxy(读写分离、分库分表、限流、流量调度)、监控报警(实时监控集群运行状态、提前上班集群风险)、High Availability(实时监控 DB 运行状态、宕机快速切换)