RDBMS【青训营笔记】

95 阅读9分钟

本文是青训营课程--后端入门--《RDBMS 基本情况介绍》的笔记,讲师为杨洋

1 经典案例

老师举了个例子,在春节红包雨的场景下,用户抢到了5元红包,那简单来讲就是两个操作

  1. 从抖音账户上扣钱
  2. 给用户账户上加钱

用SQL表示为

UPDATE account_table SET balance = balance - 5 WHERE name = '抖音';
UPDATE account_table SET balance = balance + 5 WHERE name = '用户名';

1.1 事务的ACID特性

事务是由关系型数据库提出的,是由一组SQL语句组成的一个程序执行单元,需要满足ACID特性。 将刚才提到的SQL语句用begincommit包裹起来,就成了一个事务。

BEGIN;
UPDATE account_table SET balance = balance - 5 WHERE name = '抖音';
UPDATE account_table SET balance = balance + 5 WHERE name = '用户名';
COMMIT;

ACID特性:

  • 原子性(Atomicity): 事务是一个不可再分割的工作单元,事务中的操作要么都发生,要么都不发生。
  • 一致性(Consistency): 数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
  • 隔离性(Isolation):多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果.
  • 持久性(Durability):在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚.

除了这些八股以外,老师贴心的给出了例子以便深入理解ACID。

原子性

抖音账户扣完钱之后,服务器忽然挂了,用户账户的钱没加上。 也就是说,这两个操作被分割开了,有的执行了,有的没有执行。这就是不遵循原子性的例子。 原子性能够捆绑销售,所有的操作要么一个都不做,要么全部都得做完。

一致性

抖音的账户只有1块,但是扣减5块的操作却成功了,直接把余额口成了负数。 这就破坏了一致性,一致性要保证每个操作都必须是合法的,数据库应该从一个有效状态变为另一个有效的状态

隔离性

用户同时抢到两个5元红包,这两个增加余额的操作同时到达,他们都在0的基础上加了5,结果最终用户的账户里只多了一个红包的钱。 这就是没处理好操作的并发执行,破坏了隔离性。 隔离性保证并行操作之间互不影响,表现的好像串行操作。

持久性

余额增加的操作还没更改到硬盘,服务器突然挂了,但是返回了执行成功的结果。 持久性保证更新成功后,结果应该用就行的保留下来,不会因为宕机而丢失。

1.2 好的数据库应该有的特点

高并发Concurrency

假设10亿人同时抢红包,每秒处理一个请求,那需要31年才能完成。

高可靠High Availability

保证稳定可用,避免宕机

2 发展历史

2.1 数据库发展历史

  1. 无计算机时代:还没有电脑,手写
  2. 前DBMS时代:文件系统,可以理解为打开记事本人工敲键盘记录
  3. DBMS时代:按照数据模型分为三类
    1. 网状模型:虽然能直接描述现实世界,但是结构复杂,用户不宜使用
    2. 层次模型:树状结构,同样,访问程序设计复杂,访问子结点必须访问父结点
    3. 关系模型:使用二维表描述数据。

2.2 SQL发展历史

使用类似Java这种编程语言时,我们需要细致的实现每一步要怎么做。这叫特点做过程化语言 而SQL是结构化查询语言,需要告诉他要做什么,但是不关心过程。

3 关键技术

  • SQL引擎
  • 事务引擎
  • 存储引擎

3.1 一条SQL的一生

  1. SQL通过Request路由到服务器,进入等待队列,等到线程池中有Idle worker来处理请求
  2. SQL解析器生成AST(抽象语法树),然后经过优化器成为执行计划plan交给执行器。
  3. 执行器负责读写数据并产生日志。

3.2 SQL引擎

Parser

Parser能够进行词法分析、语法分析、语义分析。 多说无益,看老师的图。 image.png

能够看到,Parser阶段就能检测出明显的错误了。

Optimizer

RBO(基于规则的优化器),比如:

  • 条件化简
  • 表连接优化,总是小表优先连接
  • Scan优化

CBO(基于代价的优化器),单个查询的代价是查询时间,总体的代价比如硬件资源利用率、吞吐量等

Executor

火山模型

执行器用的最多的是火山模型,如下图 image.png

每个Operator(算子)逐层调用Next访问下层Operator 每层的Operator计算完成后将这行数据返回给上层。 优点:

  • 每个算子独立抽象实现,互相不耦合,逻辑结构简单
  • 每计算一条数据又多次函数调用开销,导致CPU效率不高

向量化模型

image.png

和火山模型的差别是,算子返回的不再是Row,而是一批数据Batch。 优点:

  • 函数调用次数降为1n\frac{1}{n}
  • CPU cache命中率更高
  • 可以利用CPU提供的SIMD(Single Instruction Multi Data)机制

编译模型

动态编译执行技术LLVM能够将用户用到的算子打包成一个执行函数,这样就没有调用的问题。

3.3 存储引擎InnoDB

内存态

做数据缓存

Buffer Pool

以128M为单位向系统申请内存(一个Chunk) 每个Page都是16K的大小,每个Chunk128M。 将Buffer Pool划分为一个个Instance,降低Page访问冲突。 Buffer Pool在实现时需要两个重要的结构

  • 哈希表HashMap<page_id,block*>,能够通过page_id算出block在哪个哈希桶里面。
  • LRU,MySQL通过优化版的LRU释放内存空间

硬盘态

系统表空间:存储元数据(比如表名、列名、权限等) 通用表空间:存储一般的表 Undo表:存undo事务日志 Redo日志:存redo事务日志。 一些临时表

Page

在MySQL中,表格是由若干个Page组成的。每个Page是一个二进制文件,其中包含了一定数量的记录。每个记录都有一个唯一的索引值,用于在表格中进行定位。Page的大小可以通过设置参数来调整,一般情况下,每个Page的大小为1MB左右。 当我们执行查询操作时,MySQL会根据查询条件来确定需要查询的Page,并将这些Page加载到内存中,然后对这些Page中的记录进行筛选,最终返回符合条件的记录。 在MySQL中,表格的数据存储是按照B+树的结构来组织的。每个节点(即Page)都是一个B+树的节点,包含了一定数量的子节点。每个子节点可以包含多个记录,而每个记录都有一个唯一的索引值。通过B+树的结构,MySQL可以快速地定位到需要查询的Page,并从中提取出需要的记录。

B+树

1.png 页面内:页目录内使用二分法快速定位到对应的,然后再遍历该槽对应分组中的记录即可快速找到指定记录。 从根到叶:中间节点存储。

3.4 事务引擎

Undo Log与原子性

MySQL中事务的回滚是通过undo日志实现的,Undo Log是逻辑日志,记录的是数据的增量变化。利用Undo Log可以进行事务回滚,从而保证事务的原子性。同时也实现了多版本并发控制(MVCC)解决读写冲突和一致性读的问题。

MVCC与隔离性

考虑三种场景:

  • 多名读者,此时使用多个共享锁
  • 多名写者,此时使用1个排他锁
  • 有读者有写者,这时该怎么办呢?

MVCC就是数据的多版本。 新版本在page里,老版本在undo segment里,使用roll pointer组成链表,这样有读写冲突时,读者就能先读老版本数据了。 MVCC的意义:

  • 读写互不阻塞
  • 降低死锁概率
  • 实现一致性读

Redo Log和持久性

  • 如果在事务提交前写盘,会存在两个问题:
    • 随机IO:数据在磁盘上是随机分布的,对于最新的SSD、NVMe这种磁盘的随机访问能力还可以,但是对于HDD这种磁盘来讲随机访问的能力就很差
    • 写放大:写数据要修改Page(16K),可能本身只修改了几个字节,但是现在要更新16K这么大的数据。
  • WAL(Write-ahead logging):redo log是物理日志,记录页面变化,作用是保持事务持久化。如果数据写入磁盘前发生故障,重启MySQL后会根据redo log重做。

4 企业实践案例

4.1 大流量问题

最常使用Sharding解决大流量问题。 Sharding能够分库、分表。 单节点读写能力(qps每秒查询数)、容量都有上限。 Sharding能够对业务数据进行水平拆分(比如通过对主键进行哈希,或者简单根据奇偶进行分流) 用户的数据请求发送给代理层,代理层再决定将请求发送给哪个结点,整个数据库集群对外表现仍为一个数据库

4.2 流量突增

扩容

  • 问题背景:活动流量上涨,集群性能不能满足要求
  • 解决方案:扩容DB物理节点数量,利用影子表进行压测

image.png

如何实现扩容(传统方法,云原生架构下已经采用更快的方法):

  1. 首先用将老服务器数据copy到新服务器
  2. 使用binlog实时同步修改,保证二者始终一致。
  3. 代理层更改路由逻辑,将流量分配到不同服务器上
  4. 删除每个结点的冗余数据

代理连接池

用户请求变多后,连接数也会变多,代理侧会使用连接缓存。 平时会缓存一些链接,当有突发流量后,可以直接使用缓存的连接而不用新建连接。 代理连接池可以有效避免DB被徒增流量打死,避免代理和DB被大量建联打死。

4.3 稳定性&可靠性

3AZ高可用

  • 字节采用3AZ高可用,在不同的3个城市部署。
  • 使用代理,实现读写分流,分库分表,流量调度
  • 监控报警

HA管理

某个db宕机,能够快速被检测并有应对措施。