MySQL 内核原理分析(一)

  • 学一个技术,我们先要 跳出来,看整体,先要在脑中有一个这个技术的全貌。然后再 钻进去,看本质,深入的研究细节。这样方便我们建立一个立体的知识网络。不然单学多个知识点,是串不起来的。不容易记住,理解也不会深刻。
  • 所以,我们先把 MySQL 拆解一下,看看内部有哪些组件,我们 Java系统执行一条SQL,MySQL的内部是如何运作,给我们返回结果的。
  • 我们先从我们访问数据库说起
    • 我们想要查询数据库,首先得建立网络连接
    • MySQL 驱动负责建立网络连接,然后请求 MySQL 数据库
    • 其实就是创建了一个数据库连接

MySQL 内核原理分析(一)

  • Java系统的 数据库连接池
    • 如果我们的系统所有线程访问数据库时,都使用一个连接会怎样
      • 所有线程抢夺一个连接,没有连接 就操作不了数据库,效率极低,因为需要后面的线程需要等待前面的线程处理完才行

MySQL 内核原理分析(一)

    • 我们的系统如果每个线程访问数据库时,都创建一个连接,然后销毁,会怎样
      • 创建连接需要网络通信,网络通信是很耗时的
      • 好不容易创建了连接,查询完就给销毁了,那效率肯定低

MySQL 内核原理分析(一)

    • 所以,我们要使用数据库连接池
      • 数据库连接池里,会有多个数据库连接
      • 每个线程使用完连接后,会放回池子,连接不会销毁
      • 常用的数据库连接池有 DBCP、C3P0、Druid

MySQL 内核原理分析(一)

  • MySQL 的 连接器
    • Java 系统要和MySQL 建立多个连接,那 MySQL 自然也需要维护与系统之间的连接
    • 所以,MySQL 整体架构的第一个组件就是 连接器

MySQL 内核原理分析(一)

  • MySQL 连接器的功能
    • 连接器负责跟客户端建立连接、获取权限、维持和管理连接
    • 连接器内部也是一个 连接池,里面维护了各个系统跟这个数据库创建的所有连接
    • Java 系统连接Mysql 的过程
      • 首先完成TCP的三次握手,创建一个网络连接
      • 然后开始权限认证,也就是 你的 用户名 、密码 是否正确
    • 连接成功后,如果没有后续动作,这个连接会处于空闲状态
    • 空闲一定时间后,会自动断开连接,由参数 wait_timeout 控制的,默认值是 8 小时

MySQL 内核原理分析(一)

  • 我们现在已经知道,我们执行SQL,一定要先连接到数据库。数据库的 连接器 会对系统进行权限认证,如果认证成功,就创建了一个数据库连接。
  • 那么,连接之后是怎么执行SQL语句的呢?
  • 一个基本的知识点,网络连接必须要分配给一个线程去处理
    • 由一个 线程 来监听 和 读取 Java系统请求的数据
    • 线程会从网络请求中解析出我们发送的sql语句

MySQL 内核原理分析(一)

  • 线程获取到了我们写好的SQL语句,那交给谁来执行呢
    • 其实在执行之前,还有一步,就是查询缓存
    • 之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中
      • key 是查询的语句
      • value 是查询的结果
    • 如果在缓存中找到 key,那么这个 value 就会被直接返回给客户端
    • 但是,建议不要使用缓存,往往弊大于利
      • 查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空
      • 查询缓存的命中率会非常低
      • 可以将参数 query_cache_type 设置成 DEMAND,关闭缓存
    • MySQL 8.0 版本直接将查询缓存的整块功能删掉了
      • 所以接下来的图,我就不画 查询缓存 这个步骤了
    • 没有了查询缓存这个功能,我们写好的SQL,都是交给查询解析器来分析的

MySQL 内核原理分析(一)


  • 解析器
    • 我们写的SQL 语句,人认识,但是机器是不认识的,所以必须要解析我们的语句
    • 拿一条SQL举例

select id,name from user where id = 10

    • 我们的SQL 是由 字符串 和 空格 组成的
      • 有些字符串 是 MySQL 的关键字
      • MySQL 会识别这些关键字
    • 语法解析器 会将 上面的SQL拆解为几部分
      • 要从 user 表里查询数据
      • 查询 id 字段的值,等于 10 的那行数据
      • 对查询的那行数据,提取出 id 、name 两个字段
    • 如果语法不对,解析器会提示我们

ERROR 1064 (42000): You have an error in your SQL syntax;

MySQL 内核原理分析(一)


  • 优化器
    • 经过了解析器,MySQL 就知道你要做什么了,在开始执行之前,还要先经过优化器的处理,选择一个最优路径
    • 我们的表可能创建了多个索引,或者多表关联(join)的时候
      • 这时,是有多个路径可以查询到结果的,但是执行效率会不同
      • 查询优化器就是干这个事的,它会选一个效率最高的路径
    • 这个我们后面会仔细分析,这里知道它会选一个最优路径就好。先了解MySQL的整体架构,再深究细节

MySQL 内核原理分析(一)


  • 执行器
    • MySQL 通过分析器知道了你要做什么
    • 通过优化器知道了该怎么做,生成执行计划
    • 于是就进入了执行器阶段,负责这个计划的执行
    • 执行器 主要是 操作存储引擎来返回结果的,我们重点要关注的是存储引擎的执行原理

MySQL 内核原理分析(一)


  • 接下来,我们来研究一下,存储引擎的架构设计,以及如何基于存储引擎来完成一条更新语句的执行。
  • MySQL 有多种存储引擎,InnoDB、MyISAM等,我们就说最常用的InnoDB。接下来的图,我就只画 InnoDB 存储引擎这部分的了,连接、解释器、优化器会去掉,不然画不下了。
  • 我们以一条更新操作来看一下 InnoDB 的运行流程
    • 用这个SQL举例:

update users set name = '李四' where id = 10


  • InnoDB 中 重要的内存结构 Buffer Pool
    • Buffer Pool 缓冲池,是 InnoDB 存储引擎的核心组件。这里会缓存大量的数据,查询时会先看 缓冲池 内是否有这条数据,如果有,就可以不用查磁盘了
    • 如果 Buffer Pool 中没有这条数据,就要先从磁盘中加载进来

MySQL 内核原理分析(一)

  • Buffer Pool 中的数据是缓存页,磁盘中的表数据是数据页,内部有其数据结构。我们这里忽略,先看一下整体的运行流程,之后再分析里面的物理结构。

  • undo 日志文件
    • 如果我们执行一个更新语句,在没有提交事务之前,我们都是可以对数据进行回滚的
    • undo 日志文件就是保证我们可以回滚数据的一个组件
    • 举例:
      • 如果我们要把 id = 10 的数据的 name 从 张三 改为 李四
      • 第一步是把数据加载到 Buffer Pool 里
      • 第二步 就要把 id = 10 ,name = 张三 的这条原始数据,写到undo日志文件
      • 如果数据回滚,就会从 undo 日志文件中读取原始数据恢复
  • 备注:InnoDB 是个存储引擎,步骤2 其实也是我们上面说到的 执行器 来把原始数据写到磁盘上的,后面的步骤,但凡有写磁盘、读磁盘的操作,都是执行器执行的。这里为了画图方便,直接连线了

MySQL 内核原理分析(一)

  • 然后 执行器 会更新 Buffer Pool 中的缓存数据

MySQL 内核原理分析(一)


  • 现在,缓存内的数据已经从 张三 更新到 李四 了
    • 那么,现在有一个问题,如果 MySQL 此时宕机了会有问题吗
    • 因为现在还没有提交事务,代表这条语句还没执行完
    • 所以,此时宕机没有关系,事务没提交,重启后内存数据就没了,磁盘数据也没变化
    • 磁盘的数据也是原始数据,所以没关系
  • 我们在内存中修改的数据,终究要刷到磁盘上的。MySQL 不会马上把这条数据刷到磁盘上,会等系统不忙碌时,再刷回去。因为刷磁盘这事,本来实时性要就不高,我们查询的时候也是基于内存的,磁盘是什么数据无所谓,只要保证最终一致就好了。

  • 我们只有提交事务后,才能把内存修改的数据刷到磁盘上
  • 提交事务是一个过程,这个过程中我们需要先写入几份日志文件,只有这几个日志文件都写成功了,事务才算提交成功
  • 所以,这里开始介绍 InnoDB 存储引擎中的另一个组件 Redo log Buffer
    • 内存中的 Redo log Buffer 配合 磁盘上的 redo log 日志文件,可以在 MySQL 意外宕机的情况下,恢复内存数据的。它会记录内存中修改的数据,然后把这些数据刷到磁盘上的 redo log 日志中
  • 之前,我们已经修改了内存数据,在修改完成后,执行器就会向Redo log Buffer 中写入日志,到这一步为止,我们已经执行完了这条SQL语句,就差提交事务
  • 如果我们提交事务,第一步就是把 Redo log Buffer 中的日志刷到磁盘上的 redo log 中
    • 此时,如果 MySQL 宕机,数据是不会丢失的。重启后,会加载磁盘上的 redo log 日志文件,恢复到内存中
  • redo log 日志是 偏物理层面的日志,也叫 重做日志。而 binlog 是归档日志(这个后面说)
    • 为什么说是偏物理层面的日志,就是说不是给人看的,你看了也不知道修改的啥
      • 比如,对哪些数据页上的什么数据做了什么修改
  • 备注:提交事务,不是一步完成的,是一个过程。后面的步骤 5、6、7都属于提交事务的过程,只要有一步失败,那提交就是不成功的

MySQL 内核原理分析(一)

  • 把 redo log 从内存刷到磁盘的策略有三种
    • 通过参数 innodb_flush_log_at_trx_commit 来配置 ,默认值为 1
    • 值为 0 :提交事务后,不会把 redo log buffer 里的日志刷到磁盘
      • 此时如果 MySQL 宕机,redo log buffer 内数据全部丢失
    • 值为 1 :提交事务后立刻把日志刷到磁盘,只要提交事务成功,那 redo log 一定在磁盘
    • 值为 2 :提交事务后会把 redo log 先刷到 os cache(操作系统缓存) 里 ,然后 os cache 在适当的时机刷入磁盘
      • 在os cache 没刷磁盘期间,如果 MySQL 宕机,这部分数据会丢失
    • 我们平时开发还是要用 innodb_flush_log_at_trx_commit = 1 ,立刻刷磁盘。保证提交事务后,数据绝对不会丢失

  • redo log 是偏物理层面的日志。如果发生数据库操作失误,我们不能根据这个来恢复数据。我们需要用 binlog 来恢复,binlog 是偏逻辑性的日志
  • binlog
    • binlog 也叫 归档日志,是逻辑性的日志
      • 如:对 users 表的 id = 10 的一行数据做了更新操作
    • binlog 不是 InnoDB 存储引擎特有的日志文件,是属于 MySQL Server 自己的日志文件
    • 我们开始提交事务,第一步是把 redo log 日志刷到磁盘, 接下来执行器还要继续写 binlog 日志到磁盘
    • binlog 刷磁盘有两种策略,通过 sync_binlog 参数来配置,默认值 0
      • 值为 0 :先刷到 os cache 缓存,然后不定时刷入磁盘
        • 如果宕机,可能会丢失数据
      • 值为 1 :直接刷到 磁盘文件中 ,是要提交事务成功,数据一定不会丢失

MySQL 内核原理分析(一)


  • 最后,是 事务提交的最后一步
    • 执行器 会把本次更新对应的 binlog 日志的文件名 和 本次更新的 binlog 日志在文件中的位置,都写入 redo log 日志文件中
    • 同时,还会写入一个 commit 标记
    • 只有完成了这一步,才算是 事务提交成功
  • 为什么要在 redo log 中写入 commit 标记呢?
    • 用来保证 redo log 和 bin log 的数据一致性
    • 举例:
      • 如果完成了第5步,刷入了 redo log 后,MySQL 宕机了,那 bin log 就没法写入 commit 标记,那这条数据没有 commit 标记,就是无效的,提交事务失败
      • 如果第6步,刷入了 binlog 后,MySQL 宕机了,一样没有 commit 标记,也是无效的

MySQL 内核原理分析(一)


  • 现在,本条更新语句已经提交了事务,更新完毕了
    • 此时,内存上的数据 已经是 更新过的 name = 李四 ,磁盘上是 name = 张三
    • 此时,MySQL 宕机是无所谓的,数据不会丢失,重启后会从redo log 加载到缓冲池
  • 然后,是最后一个步骤
    • MySQL 有一个后台的 IO 线程,在之后的某个时间,会随机的把内存 Buffer pool 中修改的脏数据刷回磁盘的数据文件中
      • 脏数据:就是内存和磁盘不一致,但是没有什么影响

MySQL 内核原理分析(一)


  • 到现在,我们已经知道了 MySQL 的整体运行流程,和内部的运行原理,MySQL 的全貌我们已经看见了。我们在脑海中要有下面这张图

MySQL 内核原理分析(一)

  • 接下来,我们重点研究每个组件的底层原理,深入的分析里面的细节

上一篇:Mysql(3)—Mysql日志的两阶段提交分布式事务以及多事务组提交


下一篇:[20211105]索引分裂 块清除 日志增加.txt