这篇文章是关于 MySQL 面试的全方位总结,涵盖了 MySQL 底层架构与库表设计、索引机制、事务与锁机制、日志与内存、存储引擎、高级特性与性能调优、分库分表等多个方面,以面试对话的形式展开,详细阐述了相关概念、原理、实现方式、优化策略及实际应用中的问题解决等,最后以鼓励求职者的结语收尾。
一、MySQL底层架构与库表设计
问:MySQL底层架构
答:我个人理解中的Mysql整体架构,自顶向下分为连接层、服务层、引擎层以及文件层,其作用如下:
①连接层:主要是指数据库连接池,会负责处理所有客户端接入的工作,如用户登录、授权、连接等。
②服务层:这是最重要的一层,所有跨引擎的操作都会放在这里完成,如SQL解析、结果合并、执行计划生成等。
③引擎层:这依旧是MySQL较为重要的一层,服务层主要是制定执行计划和等待结果,但读写数据的具体操作都需要通过引擎层来完成,引擎层决定着表数据读写方式和存储方式。
④文件层:这是MySQL的基础层,对上层服务提供最基础的文件服务,如日志、数据、索引等文件的支持。
问:MySQL服务建立连接的呢?
答:客户端与MySQL建立连接时,会先经过TCP/IP的三次握手过程,如果采用了加密连接的方式,还会经过SSL的握手过程,握手完成后MySQL和客户端会建立session连接。接着MySQL会查询自身的mysql.user表,来验证客户端的用户名和密码,如果有误则会报错。在都正确的情况下,首先会根据登录的用户名,对客户端连接进行授权,完成后即表示连接建立成功,后续的交互会采用半全工模式通信,也就是同一时刻内,单方要么只能发送数据,要么只能接受数据。
问:客户端获取到的数据库连接本质是什么?每个连接用完后会立马被丢弃吗?
答:数据库连接的本质是一条条线程,比如当一个客户端和MySQL成功建立连接之后,MySQL会先保存客户端的网络连接信息,即session会话信息。然后为了维护与客户端之间的连接,在内部都会开启一条条的线程绑定对应的会话信息,以此来维护现有的连接,当客户端发来一条SQL语句时,维护对应连接的线程则会去执行,执行过程中也会由对应的线程处理结果集并返回。当执行完客户端的SQL语句后,MySQL默认会将连接维护八小时,在这八小时内不会销毁,除非客户端主动发送了quit指令,这时MySQL才会主动销毁连接,但这里的销毁也并非真正意义上的销毁。因为线程在任何系统中都属于珍贵资源,频繁创建和销毁的代价比较高,当客户端主动退出连接后,MySQL只会将对应线程绑定的会话信息清空,然后将“空闲”的线程放入自身的连接池当中,以备下次客户端连接时使用。
问:解析器和优化器的作用。
答:解析器和优化器一般是所有语言都具备的组件,前者主要用来词义、语义分析和语法树生成,说人话就是检测SQL语法是否正确。
优化器主要会对解析器生成的语法树,选出一套SQL执行的最优方案,如选择合适的索引、选择合适的join方式等,对于优化器最终选择的执行计划可以通过explain工具来查看。
问:MySQL执行是如何执行一条SQL语句的呢?
答:
读语句:
①先将SQL发送给SQL接口,SQL接口会对SQL语句进行哈希处理。
②SQL接口在缓存(QueryCache)中根据哈希值检索数据,如果缓存中有则直接返回数据。
③缓存中未命中时会将SQL交给解析器,解析器会判断SQL语句是否正确:
错误:抛出1064错误码及相关的语法错误信息。
正确:将SQL语句交给优化器处理,进入第④步。
④优化器根据SQL制定出不同的执行方案,并择选出最优的执行计划。
⑤工作线程根据执行计划,调用存储引擎所提供的API获取数据。
⑥存储引擎根据API调用方的操作,去磁盘中检索数据(索引、表数据....)。
⑦发送磁盘IO后,对于磁盘中符合要求的数据逐条返回给SQL接口。
⑧SQL接口会对所有的结果集进行处理(剔除列、合并数据....)并返回。
写语句:
①先将SQL发送给SQL接口,SQL接口会对SQL语句进行哈希处理。
②在缓存中根据哈希值检索数据,如果缓存中有则将对应表的所有缓存全部删除。
③经过缓存后会将SQL交给解析器,解析器会判断SQL语句是否正确:
错误:抛出1064错误码及相关的语法错误信息。
正确:将SQL语句交给优化器处理,进入第④步。
④优化器根据SQL制定出不同的执行方案,并择选出最优的执行计划。
⑤在执行开始之前,先记录一下undo-log日志和redo-log(prepare状态)日志。
⑥在缓冲区中查找是否存在当前要操作的行记录或表数据(内存中):
存在:
⑦直接对缓冲区中的数据进行写操作。
⑧然后等待后台线程将数据刷写到磁盘。
不存在:
⑦根据执行计划,调用存储引擎的API。
⑧发生磁盘IO,读取磁盘中的数据做写操作。
⑨写操作完成后,记录bin-log日志,同时将redo-log日志中的记录改为commit状态。
⑩将SQL执行耗时及操作成功的结果返回给SQL接口,再由SQL接口返回给客户端。
问:SQL执行之前会发生什么呢?
答:程序上线后,任何一条SQL语句的诞生,都源自于平台用户的操作,用户发送的请求最终会转变为一条条具体的SQL语句。生成SQL之后接着会去配置好的数据库连接池,如Druid中获取一个数据库连接,然后发给MySQL执行,但执行前还会先判断当前连接的用户,是否具备SQL要操作的表权限。
问:Druid这类连接池,和MySQL自己维护的连接池,会不会冲突呢?
答:不会呀,虽然两个都叫连接池,但一个是位于客户端,一个是位于服务端,两者的区别在于:
客户端连接池:减少多次创建数据库连接时,频繁出现的TCP三次握手、四次挥手、SSL握手等过程。
服务端连接池:减少多次创建数据库连接时,频繁创建和销毁工作线程造成的资源开销。
同时这两个连接池都能带来不小的速度提升呢,前者避免了等待网络握手的时间,后者避免了等待线程创建的时间,如果没有这些连接池,每次SQL执行时,光网络握手和创建线程就需要耗费不少时间。
问:MySQL一条线程执行完成后,它是如何知道自己该向谁返回数据的?
答:数据库连接对应的工作线程,自身会绑定客户端的会话信息,这个会话信息就包含了客户端的IP地址、端口等信息,当一条线程执行完成后,只需要根据这个地址去封装数据报文就好啦,如果要返回的结果集比较大,MySQL会把一个大的数据包拆分成多个小的数据报文分批返回。
问:有了解过数据库的三范式吗?它是做什么用的呢?
答:了解过啊,三范式主要是在设计库表结构时,需要遵循的一些原理原则:
第一范式:要求一张表的每个字段,设计时都必须具备原子性,即单个列只表示一个值,不可再分。
第二范式:要求一张表的所有字段,都必须依赖于主键,也就是一张表只能存同一个业务属性的字段。
第三范式:要求表中每一列数据不能与主键之外的字段有直接关系,也就是表中只允许一个主属性存在。