问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

你真的了解MySQL吗(从MySQL基础架构深入探究)

创作时间:
作者:
@小白创作中心

你真的了解MySQL吗(从MySQL基础架构深入探究)

引用
1
来源
1.
https://cloud.tencent.com/developer/article/2496887?policyId=20240000

MySQL,作为关系型数据库的代表,我们在开发过程中经常使用。但是,你真的了解它的底层架构吗?本文将从MySQL的基础架构出发,深入探究其各个层次和组件的工作原理,帮助你更好地理解这个常用的数据库系统。

MySQL官网架构图

从这个图中我们可以看出来,MySQL在整体架构上分为四层,分别为:

数据库连接池(MySQL Connections)

类似于Java的线程池机制,会创建一批固定的线程,当我们使用线程时直接从线程池中取线程完成连接操作,这样做的优点主要有两个:

  1. 提高获取连接的效率
  2. 减少频繁创建连接请求线程的开销

服务层(MySQL Server)

包含SQL接口、解析器、优化器、缓存层几部分

SQL接口:它其中定义了DML、DDL等语句规范,接收到SQL语句后,会负责将SQL语句转发到指定的分析器上

解析器:负责解析SQL语句正确性

优化器:通过MySQL底层的优化机制(比如优先执行选择操作或者与操作等),它底层的优化机制其实是通过语法树完成,由最简单的并、交、连接、投影等操作完成对于复杂SQL语句的解析优化:

通过并、交、连接等基础操作形成的语法树

其实在服务层中还有执行器,它的作用很简单,就是执行分析优化后的SQL语句,而一般的SQL语句会通过

SQL接口→解析器→优化器→执行器

的顺序完成SQL语句的执行

缓存层

缓存层主要分为查询区缓存写入缓存,查询数据时,如果查询区缓存中有所需数据那么就会直接返回,在修改数据时,会先将数据变更放入写入缓存中,之后MySQL底层会在合适时机将缓存中的修改操作同步到磁盘中,来减少磁盘I/O次数

虽然这么看来缓存层能够提升读写效率,但是读缓存层命中率其实并不高,因此在MySQL8.0版本中就移除了查询区缓存,但是写入缓存还是存在的

存储引擎层(Storage Engine)

这一层就应用到我们熟悉的InnoDB、MyISAM等了,关于这一层是MySQL事务、锁、MVCC、各种日志等机制实现的核心,这里我们以InnoDB存储引擎为例,看一下它的底层设计:

InnoDB存储引擎

我们可以看到,整体上存储引擎分为内存区域磁盘区域,接下来我们看一下各自的组成模块

内存区域

首先映入眼帘的就是Buffer Pool,翻译过来为缓存池,而这个缓存池中由很多部分构成:

Buffer Pool缓冲池

接下来我来简单介绍一下各个部分的作用:

Data Page数据页

每个数据页的大小默认为16KB,而数据页放到缓存中,可以带来读与写两方面的优势:

读数据时,如果数据页缓存中有该条数据,可以直接返回,无需查询磁盘数据

写数据时,通过将缓存中数据页进行变更并标记,再由后台线程进行落盘操作,可以减少磁盘I/O次数

而数据页缓存会通过LRU等缓存淘汰策略对数据页进行淘汰更新,防止缓存溢出

Index Page索引页

索引一般会建立在主键ID、高频访问的字段上面,因此通过将根索引节点保存在缓存中,这样进行查询请求时,就可以直接在索引缓存中获取根索引节点,能够减少在磁盘中访问索引节点查找根索引节点的开销

同时,索引页缓存中也不一定只保存根索引节点信息,随着时间推移,索引页中也可以保存一些经常访问的非根索引节点信息,从而加快查找效率

Lock Page

锁空间,保存了事务等操作所需的锁结构信息,这个区域的内存是有限的,如果锁实例过多,可能会出现锁粗化现象,即如果某个事务使用的是行级锁,但是由于内存不足,MySQL会将行级锁降级为表级锁

Dict Info

在创建表结构信息后,我们有时可以通过

show tables

指令获得所有创建的表,而数据库创建的表、索引、字段等信息就存储在Dict Info中,这里主要存储了四张表:

SYS_TABLES:保存了定义的表信息

SYS_COLUMNS:保存了表的字段相关信息

SYS_INDEXS:保存了索引相关信息

SYS_FIELDS:保存了索引的定义细节信息

log buffer日志缓冲区

在InnoDB架构图中,log buffer单独定义了一个区域出来,这个区域由重做日志缓冲区撤销日志缓冲区组成,在写入日志时,通过缓冲区减少磁盘I/O次数,后台会通过不同的落盘策略将缓冲区的内容写入磁盘的log文件中

关于这里有一个有意思的面试题:既然我们要写入到log文件中,为什么不直接将数据写入磁盘中?

思考这个问题的关键在于磁盘I/O,如果我们直接写入磁盘,由于每次写入磁盘都要移动旋道的指针,是随机写,但是我们写入redo log是顺序写,对于写磁盘来说,顺序写的性能远远大于随机写的性能

Adaptivity Hash自适应哈希索引

一般来说,MySQL底层存储数据会使用B+树来提升检索效率,但是通过B+树存储数据查找效率会降低为logn,因此,InnoDB底层会对查找的数据进行实时监控,如果某条数据通过索引查询次数过多,就可以为它建立一条哈希索引来提升查找效率

Insert Hash写入缓冲区

这个缓冲区主要存储更改数据库的操作,在对数据表进行修改时,会进行以下流程判断:

  1. 判断数据页缓存中是否有该数据页,如果有该数据页,那么会更改其数据并标记
  2. 反之会将更改语句放入写入缓冲区中,等到达合适时机时,会将缓冲区数据写入磁盘

这里的合适时机可以包括以下几种情况:

  1. 查询数据时,通过后台线程将缓存落盘
  2. 写入缓冲区大小不足时,通过后台线程落盘
  3. 定时刷盘线程完成刷盘操作
  4. MySQL重启或者宕机会进行刷盘操作

Change Buffer更改缓冲区

在InnoDB架构图中,我们看到其中单独设置了一块区域为Change Buffer,这个区域主要存储关于二级索引的更新操作,当修改这部分数据时,并不会直接通过磁盘IO修改数据,而是存到缓冲区中,直到读取数据到缓冲区时,再对这部分数据进行修改

以上都是关于文件数据的缓冲数据,而buffer pool中还保存着用来管理缓冲池的重要组件

Free List空闲页链表

很好理解这个链表中存储了没有使用到的空闲数据页,使用时直接从链表中获取即可

Flush List刷新页链表

由于我们在进行修改操作时,如果缓存中有对应的数据页,会在缓存中修改数据并进行标记,因此这个链表中主要就保存了有修改标记的数据页,由InnoDB引擎启动子线程将该链表的数据进行刷盘

LRU List淘汰页链表

由于缓存大小有限因此要进行页淘汰过期策略,而这个链表中就存储了对应的最长最久没有使用的页,但是由于程序运行的局部性原理,为了减少磁盘IO开销,每次进程都会读取目标数据前后的一段数据,这被称为预读现象,这就导致了两种情况:

如果读取的数据页被使用到了,能够借此减少磁盘IO开销

反之,如果数据页没有被使用,就会导致这部分数据一直在淘汰页链表中,影响了淘汰页机制

对此,InnoDB设计者借鉴了JVM的新生代与老年代概念,将

LRU List

也分为了young区old区,默认比例为63:37,首次被读取的数据会调入old区中,而young区保存的是热点数据,old区保存的是即将要淘汰的数据

这样的设计就完美了吗,不,其实还不够,如果在一个请求中,读取的页数据很大,导致淘汰页链表存储不下那么多数据怎么办,而对此,InnoDB引入了old区晋升策略:

数据被读取后,会首先放到old区中,而它晋升到young区要满足两个条件

数据页在old区待满1000ms

在调入old区1s后,数据页又再次被访问

对于这个机制其实很好理解,一般来说一个业务执行时间都很短,如果在数据页第二次被访问后就立刻调入young区,可能只是单纯一个业务对数据页进行使用,而后续业务并不会使用该数据页,而设置1s就满足了:

是另一个业务要继续使用该数据,因此该数据可能就是热数据页

应用这样的数据页更新策略,就能够很好地对缓冲池的数据页进行管理

InnoDB存储引擎

磁盘区域

以上是关于内存区域的讲解,接下来是磁盘区域,这部分的讲解相对于内存区域就少了很多

Doublewrite Buffer Files双写缓冲区文件

这个文件如其名字一样,会进行两次写入,这个文件的核心作用很简单:

我们可以先思考如下场景,当我们往数据库写入数据时,会先将数据写入位于内存区域的缓冲区中,之后会在合适时机将数据更新到磁盘中,而在开启事务时很简单,会将对应的回滚操作、重做操作等写入

undo log

redo log

中,但是如果不开启事务怎么办?

这个时候,

Doublewrite Buffer Files

就起到了关键作用:

当我们将缓冲区数据落盘时,会将数据写入

Doublewrite Buffer Files

中,这步是顺序写操作,等到合适时机再将这个文件的数据按照位置落盘到磁盘的指定区域中,这样如果写入过程中发生了崩溃重启,就可以通过

Doublewrite Buffer Files

恢复损坏的文件数据

undo log撤销日志

记录了事务开启后一系列撤销动作,在MVCC机制中,可以通过

trx_id

事务ID与

undo_rollptr_

回滚指针组成

readView

版本链,从而完成多版本并发控制,而ACID特性的原子性也由undo log保证

redo log重做日志

记录了事务开启后的物理操作,确保了事务提交后的持久化操作

tablespaces表空间

这里表空间分为了三个部分,分别为

GENERAL TABLESPACES

(通用表空间)、

TEMPORARY TABLESPACES

(临时表空间)与

FILE - PER - TABLE TABLESPACES

(单表表空间),它们的主要作用如下:

GENERAL TABLESPACES(通用表空间):当我们进行业务处理时,在进行相似的业务场景需要用到多张表,这时这些表就会放入通用表空间中,这些表共享这片资源空间,方便进行

JOIN

等跨表操作

TEMPORARY TABLESPACES(临时表空间):这个空间存储进行临时运算后存储的中间表状态,隔离临时数据,从而完成对于表连接等操作的最终处理,对此可以将临时表放到高速磁盘SSD等提升运算效率

FILE - PER - TABLE TABLESPACES(单表表空间):每个表存储的单独文件空间

好了,至此关于MySQL的基础架构就结束了,而梳理了整个MySQL架构后,我们来看一下一条语句在MySQL中执行的大致流程:

一条语句在MySQL的执行流程

我们看到,输入的MySQL语句在数据库连接池取出建立的连接后,被SQL接口转发到指定的解析器优化器执行器依次执行,同时会将执行的语句写入到Server层的写入缓存

之后进入到了InnoDB存储引擎中,进行一系列图中的操作,直至最终完成事务的提交以及日志的落盘操作

梳理完MySQL的基础架构,我们对于SQL语句的执行流程就有了更深刻的认识,希望对你有所帮助!!!

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号