1. mysql架构

image.png
mysql架构可以分为server层和存储引擎层
server层负责建立连接。包括连接器, 解析器, 优化器, 执行器等。另外内置函数,存储过程, 触发器,视图等都在server层实现。
存储引擎负责数据的存储和提取, 不同的存储引擎公用一个server层。

1.1 连接器

mysq是基于tcp协议的, 在完成三次握手后, 会建立连接, 连接器会校验名字和密码, 如果密码不对, 会收到如下报错:
image.png
如果密码正确, 会读取该用户的权限, 然后后面的逻辑判断都基于此读取的权限。
PS: 当前 MySQL 服务被多少个客户端连接了,你可以执行 show processlist.
image.png

1.2 查询缓存

如果 SQL 是查询语句(select 语句),MySQL 就会先去查询缓存( Query Cache )里查找缓存数据,看看之前有没有执行过这一条命令,这个查询缓存是以 key-value 形式保存在内存中的,key 为 SQL 查询语句,value 为 SQL 语句查询的结果。
对于更新比较频繁的表,查询缓存的命中率很低的,因为只要一个表有更新操作,那么这个表的查询缓存就会被清空。所以在1.8之后废弃缓存概念。

1.3 解析器-解析sql

词法分析: MySQL会根据你输入的字符串识别出关键字出来, 构建出 SQL 语法树,这样方面后面模块获取 SQL 类型、表名、字段名、 where 条件等等。
语法分析: 根据词法分析的结果,语法解析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。

1.4 执行sql

预处理器:

在前一步解析出来的sql语法树, 可以很方便获取表名, 字段信息等。预处理器可以判断其是否存在
image.png

优化器:

预处理之后, 优化器会针对sql语句制定一个执行计划。mysql优化器会根据成本最小原则来选择对应的索引。成本包括: io成本(把数据加载到磁盘的成本), cpu成本(检测数据是否满足条件和排序等 CPU 操作的成本)。

执行器:

执行器根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端.

2. Buffer Pool(避免IO操作)

Buffer Pool (缓冲池)是 InnoDB 存储引擎中非常重要的内存结构,我们都知道 MySQL 的数据最终是存储在磁盘中的,如果没有这个 Buffer Pool 那么我们每次的数据库请求都会磁盘中查找,这样必然会存在 IO 操作,这肯定是无法接受的。但是有了 Buffer Pool 就是我们第一次在查询的时候会将查询的结果存到 Buffer Pool 中,这样后面再有请求的时候就会先从缓冲池中去查询,如果没有再去磁盘中查找,然后在放到 Buffer Pool 中.
image.png

对于数据的更新, 是在Buffer Pool里执行, 假设此刻服务器宕机, 缓存池的数据丢失, 会造成数据丢失, mysql通过redo log解决这一问题。

2.1 redo log: 重做日志

记录数据被修改后的样子, 无论事务是否提交, 都会记录存储在存储引擎的redo log buffer中, 择机持久化到磁盘。就算突然断电了,Buffer Pool 中的数据全部丢失了,来电的时候也可以根据 redo log 恢复 Buffer Pool。
image.png
redo log buffer 中的数据写入到 redo日志文件中, 刷磁盘可以通过 innodb_flush_log_at_trx_commit 参数来设置。0表示延迟写,延迟刷, 1表示立即刷入磁盘, 2表示实时写,延迟刷. 默认是立即写入磁盘
image.png

Innodb 存储引擎的最大特点就是支持事务,如果本次更新失败,也就是事务提交失败,那么该事务中的所有的操作都必须回滚到执行前的样子. undo log解决了该问题。

2.2. undo log: 回滚日志

undo log记录了数据被修改前的数据, 若某条记录被加载到Buffer Pool的时候, 实际上会往undo log里面插入一条日志, 将原数据的值记录下来。
image.png

2.3. binlog: 归档日志

binlog 是作为mysql操作记录归档的日志,这个日志记录了所有对数据库的数据、表结构、索引等等变更的操作。也就是说只要是对数据库有变更的操作都会记录到binlog里面来。类似于银行流水。binlog以事件形式记录,不仅记录了操作的语句,同时还记录了语句所执行的消耗的时间。

2.3.1 记录内容

binlog 有三种记录格式,分别是ROW、STATEMENT、MIXED。
ROW: 基于变更行进行记录, 假设一个update有100行数据变更, 那么会记录100行对应的日志。
STATEMENT: 相对于ROW模式, STATEMENT模式下只会记录这个update 的语句. 节省日志空间。
MIXED: 混合使用. 一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog。

2.3.2 写入策略

sync_binlog=0,表示每次提交事务binlog不会马上写入到磁盘,而是先写到page cache,相对于磁盘写入来说写page cache要快得多,不过在Mysql 崩溃的时候会有丢失日志的风险。
sync_binlog=1(强一致),表示每次提交事务都会执行 fsync 写入到磁盘 ;
sync_binlog>1,表示每次提交事务都 先写到page cach,只有等到积累了N个事务之后才fsync 写入到磁盘,同样在此设置下Mysql 崩溃的时候会有丢失N个事务日志的风险。

2.3.3 与redo log区别:

image.png

比如update tb_user set age =18 where name =’赵白’ ,如果这条语句修改了三条记录的话。
那么binlog记录是:

  1. UPDATE `db_test`.`tb_user` WHERE @1=5 @2='赵白' @3=91 @4='1543571201' SET @1=5 @2='赵白' @3=18 @4='1543571201'
  2. UPDATE `db_test`.`tb_user` WHERE @1=6 @2='赵白' @3=91 @4='1543571201' SET @1=5 @2='赵白' @3=18 @4='1543571201'
  3. UPDATE `db_test`.`tb_user` WHERE @1=7 @2='赵白' @3=91 @4='1543571201' SET @1=5 @2='赵白' @3=18 @4='1543571201'

redo log则是记录着磁盘数据的变更日志,
把表空间10、页号5、偏移量为10处的值更新为18。
把表空间11、页号1、偏移量为2处的值更新为18。
把表空间12、页号2、偏移量为9处的值更新为18。

2.4. 数据更新流程

image.png
从磁盘读取记录,放到内存。
记录undo log 日志。
修改内存中的记录。
记录redo log (预提交状态)
记录binlog
提交事务,写入redo log (commit状态)

2.4.1 二阶段提交问题

我们可以假设不采⽤两阶段提交的⽅式,⽽是采⽤“单阶段”进⾏提交,即要么先写⼊redo
log,后写⼊binlog;要么先写⼊binlog,后写⼊redo log。这两种⽅式的提交都会导致原先数
据库的状态和被恢复后的数据库的状态不⼀致。

先写binlog,再写redo log

当前事务提交后,写入binlog成功,之后主节点崩溃。在主节点重启后,由于没有写入redo log,因此不会恢复该条数据。
而从节点依据binlog在本地回放后,会相对于主节点多出来一条数据,从而产生主从不一致。

先写redo log,再写binlog

当前事务提交后,写入redo log成功,之后主节点崩溃。在主节点重启后,主节点利用redo log进行恢复,就会相对于从节点多出来一条数据,造成主从数据不一致。
因此,只写一次redo log与binlog,无法保证这两种日志在事务提交后的一致性。也就是无法保证主节点崩溃恢复与从节点本地回放数据的一致性。

在两阶段提交的情况下,是怎么实现崩溃恢复的呢?

首先比较重要的一点是,在写入redo log时,会顺便记录XID,即当前事务id。在写入binlog时,也会写入XID。
如果在写入redo log之前崩溃,那么此时redo log与binlog中都没有,是一致的情况,崩溃也无所谓。
如果在写入redo log prepare阶段后立马崩溃,之后会在崩恢复时,由于redo log没有被标记为commit。于是拿着redo log中的XID去binlog中查找,此时肯定是找不到的,那么执行回滚操作。
如果在写入binlog后立马崩溃,在恢复时,由redo log中的XID可以找到对应的binlog,这个时候直接提交即可。
总的来说,在崩溃恢复后,只要redo log不是处于commit阶段,那么就拿着redo log中的XID去binlog中寻找,找得到就提交,否则就回滚。在这样的机制下,两阶段提交能在崩溃恢复时,能够对提交中断的事务进行补偿,来确保redo log与binlog的数据一致性。

参考

https://blog.csdn.net/qq_33591903/article/details/122030252