6

全解MySQL之架构篇:自顶向下深入剖析MySQL整体架构!

 1 year ago
source link: https://blog.51cto.com/u_15773567/5794258
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

二、MySQL整体结构浅析

本章作为​​MySQL​​系列的开篇之作,当然也有一定的原因,毕竟只有先对​​MySQL​​的整体架构有了一个宏观的认知,才能更好的理解每个细节点的知识。

​MySQL​​与我们开发项目时相同,为了能够合理的规划整体架构设计,也会将整个​​MySQL​​服务抽象成几个大的模块,然后在内部进行实现,因此先来看看​​MySQL​​的整体架构,开局先上一张图:

全解MySQL之架构篇:自顶向下深入剖析MySQL整体架构!_客户端

从上往下看,依次会分为网络连接层、系统服务层、存储引擎层、以及文件系统层,往往编写​​SQL​​后,都会遵守着​​MySQL​​的这个架构往下走。

  • 连接层:主要是指数据库连接池,会负责处理所有客户端接入的工作。
  • 服务层:主要包含​​SQL​​接口、解析器、优化器以及缓存缓冲区四块区域。
  • 存储引擎层:这里是指​​MySQL​​支持的各大存储引擎,如​​InnoDB、MyISAM​​等。
  • 文件系统层:涵盖了所有的日志,以及数据、索引文件,位于系统硬盘上。

OK~,除了上述的四层外,还有客户端,这个客户端可以是各类编程语言,如​​Java、Go、Python、C/C++、PHP、Node、.Net....​​,也可以是一些数据库的可视化软件,例如​​Navicat、SQLyog​​等,也可以是​​mysql-cli​​命令行工具。总之,只要能与​​MySQL​​建立网络连接,都可以被称为是​​MySQL​​的客户端。

​MySQL-Server​​就是上述图中的那玩意儿,一般来说,客户端负责编写​​SQL​​,而服务端则负责​​SQL​​的执行与数据的存储。

对​​MySQL​​的整体架构有了简单了解后,接下来详细的拆解一下​​MySQL-Server​​的每个层面。

三、网络连接层

当一个客户端尝试与​​MySQL​​建立连接时,​​MySQL​​内部都会派发一条线程负责处理该客户端接下来的所有工作。而数据库的连接层负责的就是所有客户端的接入工作,​​MySQL​​的连接一般都是基于​​TCP/IP​​协议建立网络连接,因此凡是可以支持​​TCP/IP​​的语言,几乎都能与​​MySQL​​建立连接。

其实​​MySQL​​还支持另一种连接方式,就是​​Unix​​系统下的​​Socket​​直连,但这种方式一般使用的较少。

虽然​​MySQL​​是基于​​TCP/IP​​协议栈实现的连接建立工作,但并非使用​​HTTP​​协议建立连接的,一般建立连接的具体协议,都会根据不同的客户端实现,如​​jdbc、odbc...​​这类的。在这里先暂且不纠结连接​​MySQL​​时的协议类型,先来看看一般是怎么连接​​MySQL​​的?如下:

​mysql -h 127.0.0.1 -uroot -p123456​

例如上述这条指令,​​-h​​表示​​MySQL​​所在的服务器​​IP​​地址,​​-u​​表示本次连接所使用的用户名,​​-p​​则代表着当前用户的账号密码,当执行这条指令后,会与​​MySQL-Server​​建立网络连接。当然,​​MySQL​​也支持​​SSL​​加密连接,如果采用这种方式建立连接,那还会经过[《SSL多次握手过程》,当握手结束,网络建立成功后,则会开始正式的数据库连接建立工作。

​TCP​​网络连接建立成功后,​​MySQL​​服务端与客户端之间会建立一个​​session​​会话,紧接着会对登录的用户名和密码进行效验,​​MySQL​​首先会查询自身的用户表信息,判断输入的用户名是否存在,如果存在则会判断输入的密码是否正确,如若密码错误或用户名不存在就会返回​​1045​​的错误码,如下信息:

​ERROR 1045 (28000): Access denied for user 'zhuzi'@'localhost' (using password: YES)​

如果你在连接数据库的过程中,出现了上述的错误信息,那绝对是你输入的用户名或密码错误导致的,当账号及密码正确时,此时就会进入​​MySQL​​的命令行,接下来可以执行​​SQL​​操作。

但实际上,在用户名和密码都正确的情况下,​​MySQL​​还会做一些些小动作,也就是会进行授权操作,查询每个用户所拥有的权限,并对其授权,后续​​SQL​​执行时,都会先判断是否具备执行相应​​SQL​​语句的权限,然后再执行。

OK~,经过上述流程后数据库连接就建立成功了,数据库连接建立成功后,​​MySQL​​与客户端之间会采用半全工的通讯机制工作,与之对应的还有“双全工、单工”的工作模式:

  • 双全工:代表通讯的双方在同一时间内,即可以发送数据,也可以接收数据。
  • 半全工:代表同一时刻内,单方要么只能发送数据,要么只能接受数据。
  • 单工:当前连接只能发送数据或只能接收数据,也就是“单向类型的通道”。

到这里,​​MySQL​​也会“安排”一条线程维护当前客户端的连接,这条线程也会时刻标识着当前连接在干什么工作,可以通过​​show processlist;​​命令查询所有正在运行的线程:

执行结果如下(​​root​​账号可以查询所有线程):
全解MySQL之架构篇:自顶向下深入剖析MySQL整体架构!_mysql_02
  • ​Id​​:当前线程的​​ID​​值,可以利用这个​​ID​​,使用​​kill​​强杀线程。
  • ​User​​:当前线程维护的数据库连接,与之对应的用户是谁。
  • ​Host​​:与当前线程保持连接关系的客户端地址(​​IP+Port​​)。
  • ​db​​:目前线程在哪个数据库中执行​​SQL​​。
  • ​Command​​:当前线程正在执行的​​SQL​​类型,如:
  • ​Create DB​​:正在执行创建数据库的操作。
  • ​Drop DB​​:正在执行删除数据库的操作。
  • ​Execute​​:正在执行预编译的​​SQL​​(​​PreparedStatement​​)。
  • ​Close Stmt​​:正在关闭一个​​PreparedStatement​​。
  • ​Query​​:正在执行普通的​​SQL​​语句。
  • ​Sleep​​:正在等待客户端发送​​SQL​​语句。
  • ​Quit​​:当前客户端正在退出连接。
  • ​Shutdown​​:正在关闭​​MySQL​​服务端。
  • ​Time​​:表示当前线程处于目前状态的时间,单位是秒。
  • ​State​​:表示当前线程的状态,有如下几种:
  • ​Updating​​:当前正在执行​​update​​语句,匹配数据做修改操作。
  • ​Sleeping​​:正在等待客户端发送新的​​SQL​​语句。
  • ​Starting​​:目前正在处理客户端的请求。
  • ​Checking table​​:目前正在表中查询数据。
  • ​Locked​​:当前线程被阻塞,其他线程获取了执行需要的锁资源。
  • ​Sending Data​​:目前执行完成了​​Select​​语句,正在将结果返回给客户端。
  • ​Info​​:一般记录当前线程正在执行的​​SQL​​,默认显示前一百个字符,查看完整的​​SQL​​可以使用​​show full processlist;​​命令。

其实从这个结果上来看,我们能够很明显的看到数据库中各个线程的信息,这条指令对于以后做线上排查时有很大的作用,目前先简单了解,接着来看看数据库连接池。

3.1、数据库连接池(Connection Pool)

​Connection Pool​​翻译过来的意思就是连接池,那为什么需要有这个东西呢?因为前面聊到过,所有的客户端连接都需要一条线程去维护,而线程资源无论在哪里都属于宝贵资源,因此不可能无限量创建,所以这里的连接池就相当于​​Tomcat​​中的线程池,主要是为了复用线程、管理线程以及限制最大连接数的。

连接池的最大线程数可以通过参数​​max-connections​​来控制,如果到来的客户端连接超出该值时,新到来的连接都会被拒绝,关于最大连接数的一些命令主要有两条:

  • ​show variables like '%max_connections%';​​:查询目前​​DB​​的最大连接数。
  • ​set GLOBAL max_connections = 200;​​:修改数据库的最大连接数为指定值。

对于不同的机器配置,可以适当的调整连接池的最大连接数大小,以此可以在一定程度上提升数据库的性能。除了可以查询最大连接数外,​​MySQL​​本身还会对客户端的连接数进行统计,对于这点可以通过命令​​show status like "Threads%";​​查询:

全解MySQL之架构篇:自顶向下深入剖析MySQL整体架构!_客户端_03

其中各个字段的释义如下:

  • ​Threads_cached​​:目前空闲的数据库连接数。
  • ​Threads_connected​​:当前数据库存活的数据库连接数。
  • ​Threads_created​​:​​MySQL-Server​​运行至今,累计创建的连接数。
  • ​Threads_running​​:目前正在执行的数据库连接数。

对于几个字段很容易理解,额外要说明的一点是​​Threads_cached​​这个字段,从名称上来看,似乎跟缓存有关系,其实也没错,因为这里是有一个数据库内部的优化机制。当一个客户端连接断开后,对于数据库连接却不会立马销毁,而是会先放入到一个缓存连接池当中。这样就能在下次新连接到来时,省去了创建线程、分配栈空间等一系列动作,但这个值不会是无限大的,一般都在​​32​​左右。

连接池的优化思想与​​Java​​线程池相同,会将数据库创建出的连接对象放入到一个池中,一旦出现新的访问请求会复用这些连接,一方面提升了性能,第二方面还节省了一定程度上的资源开销。

四、系统服务层

学习了​​MySQL​​网络连接层后,接下来看看系统服务层,​​MySQL​​大多数核心功能都位于这一层,包括客户端​​SQL​​请求解析、语义分析、查询优化、缓存以及所有的内置函数(例如:日期、时间、统计、加密函数...),所有跨引擎的功能都在这一层实现,譬如存储过程、触发器和视图等一系列服务。

全解MySQL之架构篇:自顶向下深入剖析MySQL整体架构!_sql_04

也就是上述这几部分,主要包含​​SQL​​接口、解析器、优化器以及缓存相关的这些部分。当然,也许你会问我还有一个[管理服务&工具组件]呢,这块其实属于全局的,属于​​MySQL​​的基础设施服务,接下来一个个的讲一下服务层的各个细节吧。

4.1、SQL接口

​SQL​​接口组件,这个名词听上去似乎不太容易理解,其实主要作用就是负责处理客户端的​​SQL​​语句,当客户端连接建立成功之后,会接收客户端的​​SQL​​命令,比如​​DML、DDL​​语句以及存储过程、触发器等,当收到​​SQL​​语句时,​​SQL​​接口会将其分发给其他组件,然后等待接收执行结果的返回,最后会将其返回给客户端。

简单来说,也就是​​SQL​​接口会作为客户端连接传递​​SQL​​语句时的入口,并且作为数据库返回数据时的出口。

对于这个组件没太多好聊的,简单展开两点叙述一下后就结束这个话题,第一点是对于​​SQL​​语句的类型划分,第二点则是触发器。在​​SQL​​中会分为五大类:

  • ​DML​​:数据库操作语句,比如​​update、delete、insert​​等都属于这个分类。
  • ​DDL​​:数据库定义语句,比如​​create、alter、drop​​等都属于这个分类。
  • ​DQL​​:数据库查询语句,比如最常见的​​select​​就属于这个分类。
  • ​DCL​​:数据库控制语句,比如​​grant、revoke​​控制权限的语句都属于这个分类。
  • ​TCL​​:事务控制语句,例如​​commit、rollback、setpoint​​等语句属于这个分类。

再来聊一聊​​MySQL​​的触发器,这东西估计大部分小伙伴没用过,但它在有些情景下还较为实用,不过想要了解触发器是什么,首先咱们还得先理解存储过程。

存储过程:是指提前编写好的一段较为常用或复杂​​SQL​​语句,然后指定一个名称存储起来,然后先经过编译、优化,完成后,这个“过程”会被嵌入到​​MySQL​​中。

也就是说,[存储过程]的本质就是一段预先写好并编译完成的​​SQL​​,而我们要聊的触发器则是一种特殊的存储过程,但[触发器]与[存储过程]的不同点在于:存储过程需要手动调用后才可执行,而触发器可由某个事件主动触发执行。在​​MySQL​​中支持​​INSERT、UPDATE、DELETE​​三种事件触发,同时也可以通过​​AFTER、BEFORE​​语句声明触发的时机,是在操作执行之前还是执行之后。

说简单一点,[​​MySQL​​触发器]就类似于​​Spring​​框架中的​​AOP​​切面。

OK~,至此就先打住,对于这些概念暂且了解到这里,后续会专门去聊​​MySQL​​的存储过程、触发器、视图等这些特殊的操作。

4.2、解析器

客户端连接发送的​​SQL​​语句,经过​​SQL​​接口后会被分发到解析器,解析器这东西其实在所有语言中都存在,​​Java、C、Go...​​等其他语言都有,解析器的作用主要是做词法分析、语义分析、语法树生成...这类工作的,如果对于这个具体过程感兴趣,可以参考之前的《JVM-执行引擎子系统-Javac编译过程》,​​Java​​源码在编写后,会经历这个过程,​​SQL​​语言同样类似。

而解析器这一步的作用主要是为了验证​​SQL​​语句是否正确,以及将​​SQL​​语句解析成​​MySQL​​能看懂的机器码指令。稍微拓展一点大家就明白了,好比如我们编写如下一条​​SQL​​:

​select * form user;​

然后运行会得到如下错误信息:

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

在上述​​SQL​​中,我们将​​from​​写成了​​form​​,结果运行时​​MySQL​​提升语法错误了,​​MySQL​​是如何发现的呢?就是在词法分析阶段,检测到了存在语法错误,因此抛出了对应的错误码及信息。当然,如果​​SQL​​正确,则会进行下一步工作,生成​​MySQL​​能看懂的执行指令。

4.3、优化器

解析器完成相应的词法分析、语法树生成....等一系列工作后,紧接着会来到优化器,优化器的主要职责在于生成执行计划,比如选择最合适的索引,选择最合适的​​join​​方式等,最终会选择出一套最优的执行计划。

当然,在这里其实有很多资料也会聊到,存在一个执行器的抽象概念,实际上执行器是不存在的,因此前面聊到过,每个客户端连接在​​MySQL​​中都用一条线程维护,而线程是操作系统的最小执行单位,因此所谓的执行器,本质上就是线程本身。

优化器生成了执行计划后,维护当前连接的线程会负责根据计划去执行​​SQL​​,这个执行的过程实际上是在调用存储引擎所提供的​​API​​。

4.4、缓存&缓冲

这块较为有趣,主要分为了读取缓存与写入缓冲,读取缓存主要是指​​select​​语句的数据缓存,当然也会包含一些权限缓存、引擎缓存等信息,但主要还是​​select​​语句的数据缓存,​​MySQL​​会对于一些经常执行的查询​​SQL​​语句,将其结果保存在​​Cache​​中,因为这些​​SQL​​经常执行,因此如果下次再出现相同的​​SQL​​时,能从内存缓存中直接命中数据,自然会比走磁盘效率更高,对于​​Cache​​是否开启可通过命令查询。

  • ​show global variables like "%query_cache_type%";​​:查询缓存是否开启。
  • ​show global variables like "%query_cache_size%";​​:查询缓存的空间大小。

同时还可以通过​​show status like'%Qcache%';​​命令查询缓存相关的统计信息。

全解MySQL之架构篇:自顶向下深入剖析MySQL整体架构!_mysql_05
  • ​Qcache_free_blocks​​:查询缓存中目前还有多少剩余的​​blocks​​。
  • ​Qcache_free_memory​​:查询缓存的内存大小。
  • ​Qcache_hits​​:表示有多少次查询​​SQL​​命中了缓存。
  • ​Qcache_inserts​​:表示有多少次查询​​SQL​​未命中缓存然后走了磁盘。
  • ​Qcache_lowmem_prunes​​:这个值表示有多少条缓存数据从内存中被淘汰。
  • ​Qcache_not_cached​​:表示由于自己设置了缓存规则后,有多少条数据不符合缓存条件。
  • ​Qcache_queries_in_cache​​:表示当前缓存中缓存的数据数量。
  • ​Qcache_total_blocks​​:当前缓存区中​​blocks​​的数量。

当然,由于我是​​MySQL5.7​​版本,因此对于这些依旧可以查询到,但是在高版本的​​MySQL​​中,移除了查询缓存区,毕竟命中率不高,而且查询缓存这一步还要带来额外开销,同时一般程序都会使用​​Redis​​做一次缓存,因此结合多方面的原因就移除了查询缓存的设计。

简单了解了查询缓存后,再来看看写入缓冲,这也是我说的比较有趣的点,缓冲区的设计主要是:为了通过内存的速度来弥补磁盘速度较慢对数据库造成的性能影响。在数据库中读取某页数据操作时,会先将从磁盘读到的页存放在缓冲区中,后续操作相同页的时候,可以基于内存操作。

一般来说,当你对数据库进行写操作时,都会先从缓冲区中查询是否有你要操作的页,如果有,则直接对内存中的数据页进行操作(例如修改、删除等),对缓冲区中的数据操作完成后,会直接给客户端返回成功的信息,然后​​MySQL​​会在后台利用一种名为​​Checkpoint​​的机制,将内存中更新的数据刷写到磁盘。

​MySQL​​在设计时,通过缓冲区能减少大量的磁盘​​IO​​,从而进一步提高数据库整体性能。毕竟每次操作都走磁盘,性能自然上不去的。

PS:后续高版本的MySQL​移除了查询缓存区,但并未移除缓冲区,这是两个概念,请切记!

同时缓冲区是与存储引擎有关的,不同的存储引擎实现也不同,比如​​InnoDB​​的缓冲区叫做​​innodb_buffer_pool​​,而​​MyISAM​​则叫做​​key_buffer​​。

五、存储引擎层

存储引擎也可以理解成​​MySQL​​最重要的一层,在前面的服务层中,聚集了​​MySQL​​所有的核心逻辑操作,而引擎层则负责具体的数据操作以及执行工作。

如果有小伙伴研究过​​Oracle、SQLServer​​等数据库的实现,应该会发现这些数据库只有一个存储引擎,因为它们是闭源的,所以仅有官方自己提供的一种引擎。而​​MySQL​​则因为其开源特性,所以存在很多很多款不同的存储引擎实现,​​MySQL​​为了能够正常搭载不同的存储引擎运行,因此引擎层是被设计成可拔插式的,也就是可以根据业务特性,为自己的数据库选择不同的存储引擎。

​MySQL​​的存储引擎主要分为官方版和民间版,前者是​​MySQL​​官方开发的,后者则是第三方开发的。存储引擎在​​MySQL​​中,相关的规范标准被定义成了一系列的接口,如果你也想要使用自己开发的存储引擎,那么只需要根据​​MySQL AB​​公司定义的准则,编写对应的引擎实现即可。

​MySQL​​目前有非常多的存储引擎可选择,其中最为常用的则是​​InnoDB​​与​​MyISAM​​引擎,可以通过​​show variables like '%storage_engine%';​​命令来查看当前所使用的引擎。其他引擎如下:

全解MySQL之架构篇:自顶向下深入剖析MySQL整体架构!_mysql_06

存储引擎是​​MySQL​​数据库中与磁盘文件打交道的子系统,不同的引擎底层访问文件的机制也存在些许细微差异,引擎也不仅仅只负责数据的管理,也会负责库表管理、索引管理等,​​MySQL​​中所有与磁盘打交道的工作,最终都会交给存储引擎来完成。

后续也会有专门的文章详细聊到​​MySQL​​的存储引擎,这里先简单了解即可。

六、文件系统层

全解MySQL之架构篇:自顶向下深入剖析MySQL整体架构!_sql_07

这一层则是​​MySQL​​数据库的基础,本质上就是基于机器物理磁盘的一个文件系统,其中包含了配置文件、库表结构文件、数据文件、索引文件、日志文件等各类​​MySQL​​运行时所需的文件,这一层的功能比较简单,也就是与上层的存储引擎做交互,负责数据的最终存储与持久化工作。

这一层主要可分为两个板块:①日志板块。②数据板块。

6.1、日志模块

在​​MySQL​​中主要存在七种常用的日志类型,如下:

  • ①​​binlog​​二进制日志,主要记录​​MySQL​​数据库的所有写操作(增删改)。
  • ②​​redo-log​​重做/重写日志,​​MySQL​​崩溃时,对于未落盘的操作会记录在这里面,用于重启时重新落盘(​​InnoDB​​专有的)。
  • ③​​undo-logs​​撤销/回滚日志:记录事务开始前[修改数据]的备份,用于回滚事务。
  • ④​​error-log​​:错误日志:记录​​MySQL​​启动、运行、停止时的错误信息。
  • ⑤​​general-log​​常规日志,主要记录​​MySQL​​收到的每一个查询或​​SQL​​命令。
  • ⑥​​slow-log​​:慢查询日志,主要记录执行时间较长的​​SQL​​。
  • ⑦​​relay-log​​:中继日志,主要用于主从复制做数据拷贝。

上述列出了​​MySQL​​中较为常见的七种日志,但实际上还存在很多其他类型的日志,不过一般对调优、排查问题、数据恢复/迁移没太大帮助,用的较少,因此不再列出。

6.2、数据模块

前面聊到过,​​MySQL​​的所有数据最终都会落盘(写入到磁盘),而不同的数据在磁盘空间中,存储的格式也并不相同,因此再列举出一些​​MySQL​​中常见的数据文件类型:

  • ​db.opt​​文件:主要记录当前数据库使用的字符集和验证规则等信息。
  • ​.frm​​文件:存储表结构的元数据信息文件,每张表都会有一个这样的文件。
  • ​.MYD​​文件:用于存储表中所有数据的文件(​​MyISAM​​引擎独有的)。
  • ​.MYI​​文件:用于存储表中索引信息的文件(​​MyISAM​​引擎独有的)。
  • ​.ibd​​文件:用于存储表数据和索引信息的文件(​​InnoDB​​引擎独有的)。
  • ​.ibdata​​文件:用于存储共享表空间的数据和索引的文件(​​InnoDB​​引擎独有)。
  • ​.ibdata1​​文件:这个主要是用于存储​​MySQL​​系统(自带)表数据及结构的文件。
  • ​.ib_logfile0/.ib_logfile1​​文件:用于故障数据恢复时的日志文件。
  • ​.cnf/.ini​​:​​MySQL​​的配置文件,​​Windows​​下是​​.ini​​,其他系统大多为​​.cnf​​。
  • ​......​

上述列举了一些​​MySQL​​中较为常见的数据文件类型,无论是前面的日志文件,亦或是现在的数据文件,这些都是后续深入剖析​​MySQL​​时会遇到的,因此在这里先有个简单认知,方便后续更好的理解​​MySQL​​。

当然,上述并没有完全列出​​MySQL​​所有的日志类型和文件类型,大家有兴趣的可以去自行翻看一下安装​​MySQL​​的目录,你会找其中找到很多其他类型的日志或数据文件~

七、MySQL架构篇小结

看到这里,《​​MySQL​​架构篇》就已经接近尾声啦,本文的主要目的是在于先对​​MySQL​​的整体架构有一个基本认知,这也为咱们后续的文章打下了坚实的基础,因为毕竟想要深入研究一个技术,那定然不能如同管中窥豹一般,仅看一个细节点,而是更应该是先窥其全貌,再深入细节。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK