阿钊的写字板

专注于 Java 技术栈与分布式系统实践,分享技术难题攻克、系统优化实战与信创项目开发经验。

【高性能MySQL:第一章节(MySQL架构与历史)】学习小节

本章解释了MySQL的架构及其存储引擎的关键设计,也介绍一点MySQL的历史背景、版本演进、开发模式等信息。

问题一:为什么已经有了这么多的数据库,还会有MySQL这个产物出现?
MySQL 遵循 GPL 开源协议意味着每个用户可以自由的使用 MySQL 源代码,通过这种方式可以促进开源社区的生态,推动代码持续开放和社区协作,每个用户都可以获取其他人的贡献也可以向其他人提供帮助,以此形成正向循环。开发者也可以向 GPL 项目贡献代码,提高自己的个人影响力其劳动成果受制于 GPL 的约束也不会被商业公司独占。大厂可以通过 GPL 源码来研发适合自己业务的数据库系统,降低研发成本,因为从头开发写一个数据库成本是巨大的,但如果有现成且很不错的轮子再去根据需要去改的话,成本就会降低太多。但需要注意是大厂修改的这部分 MySQL 源码也需要开源,否则可能会带来一些不必要的法律问题。

问题二:MySQL 的架构思想是什么样的?
MySQL 通过分层架构的思想,将查询处理及其他系统任务和数据存取相分离,使得各层的内部组件可以协同工作、各司其职,这样可以显著提高处理效率,也有利于架构的整洁性、可维护性和可扩展性。
MySQL 的架构从总体上看分为三个层面:客户端、服务器层、存储引擎。
客户端负责面向客户的连接处理、授权认证、安全访问等功能。
服务器层是 MySQL 的一个核心服务,包含查询解析器、词法分析器、语法分析器、优化器、查询缓存、执行器、以及 MySQL相关的内置函数、存储过程、触发器、视图等。服务器层主要目的是解决与存储引擎之间的交互。因为 MySQL 支持不同的存储引擎,比如支持事务处理的 InnoDB 引擎,和非事务的 MyISAM 引擎。以及基于内存处理的 Memory 引擎,所以跨存储引擎的功能都在服务器层进行处理。
存储引擎负责数据的存储和提取,管理数据以什么方式持久化到硬盘上并且不会丢失的问题,以及如何管理数据的可靠性问题。存储引擎不会去解析 SQL ,也不会去和其他的存储引擎进行通信,只会处理和响应来自服务器层的 API 请求。
从查询过程上来看,整个过程应该是这样的:

  1. 当一个客户端与 MySQL 服务器通过 TCP 的三次握手建立连接时,一次会话就开始了,客户端首先需要通过正确的用户名、密码来通过 MySQL 的连接检查和授权认证,以此来证明我是有许可授权的。
  2. 当连接成功并鉴权通过时,用户就获得了会话授权,此时用户就可以发送 SQL 指令来给 MySQL 服务器下达指令我要干什么。
  3. 当 MySQL 服务器层获取用户下达的 SQL 指令时服务层就需要通过内部的机制来去理解指令的意图。那么就需要对 SQL 指令进行解读,通过词法分析来对关键词进行拆解判断指令要做的动作、通过语法分析器来判断指令有没有不能被理解的部分、通过优化器来判断应该怎么执行会更高效,再去查询缓存中以 SQL 指令为key 去判断是否有执行过的记录,如果有就把缓存过的查询结果返回,如果没有就去走执行器跟存储引擎进行 API 交互来获取符合条件的数据并将查询结果缓存到查询缓存中并将查询结果返回给客户端,一次整个的查询过程就结束掉了。

问题三:MySQL 是如何解决多个会话同时访问同一数据的问题的?
多个会话在同一时间访问同一数据的问题不单单是指查询的操作,也可能是修改、删除、新增等业务场景。就像一个门同时只能允许一个人通过,但此时如果两个人都要通过这个门,如果两个人都不谦让的情况在,那么最终的结果就是大家都只能耗着谁都过不去,这个过程称之为“死锁”。如果此时有一个人主动避让这个循环就将被打破,大家也都能高效率的通过门。
但当多个用户在同一时刻去操作同一资源,尤其是一个是查询,一个是删除或者更新操作的时候就会出现非幂等性的问题进而产生脏读和幻读问题。MySQL 的查询操作本身就是幂等性的,因为不论查询多少次,在数据不被修改的前提下数据返回的最终结果都是一致的。幻读和脏读问题的本质其实就是数据的并发控制问题和数据的隔离问题。MySQL 在服务器层面通过锁机制来解决数据的并发控制问题,在存储引擎层面通过锁机制和事务的隔离级别和 MVCC 事务版本号机制来解决数据脏读和幻读问题。
锁机制的实现跟银行柜台窗口的排队机制是很像的,在银行柜台窗口都有空闲且无人排队时,那么你到哪个窗口办理业务都是可以的,因为不存在柜台窗口的资源竞争性问题。但如果当人多了超过柜台所接待的最大人数,就需要通过取号系统来排队等号。当柜台窗口的用户在办业务时,后面的用户都需要排队等待。当柜台用户办理完业务时,用户所取的号码就会从独占状态释放,柜台用户号码的下一个用户才能到窗口办理业务并独占柜台资源,此时号码会转换为独占状态,下一个编号就进入等待状态,如果下一个编号被连续叫号 3 次没有用户应答,该编号就会被释放,延续到下个编号的用户。那么这个锁其实就是排他锁。只不过银行通过队列 + 锁解决了柜台资源的竞争性问题。
MySQL 通过表锁来实现数据竞争性的管理。一个用户在对表进行写操作前,需要先获取写锁,这会阻塞其他用户对该表的所有读写操作,也就是此时用户在释放锁前对该表是独占的。当没有写锁时,其他读锁的用户才能获取读锁,在当前用户获取读锁的同时,其他用户也是可以获取的。
行级锁的优点是其锁粒度更小,因为只需要锁定需要的数据航,锁粒度更小意味着影响的数据量会小很多,也会支持更大的并发量,但缺点是维护的成本也会更高,比如:检查锁、获取锁、判断锁的状态、释放锁。但这些都是存储引擎自身去管理和维护的。
锁机制在一定程度上确实解决了数据访问的竞争问题,但是在某些特定的场景下锁机制也不是万能的,比如张三从银行账户转账 1000 元给李四,就需要这几个步骤:

  1. 首先张三的账户上有没有 1000 元,如果没有则余额不足没办法转账;
  2. 从张三的账户中扣减 1000 元;
  3. 李四的账户中增加 1000 元;
    此时只有当三个操作都成功的情况下整个的转账流程才算完成,但如果有一步不满足要求都不能算成功,此时单纯的通过锁机制很难实现这个逻辑,为了解决这个问题就需要事务来完成,只有当整个事务都成功时转账的操作才算完成。事务通过隔离级别的控制以达到隔离访问的目的,这样的好处是通过隔离级别的控制让其他事务能在什么情况下看到数据的更改。比如说当处于“未提交读”的情况下,即使事务没有提交,其他事务也是可以看到的,此时其他事务如果读到没有提交或者撤掉提交前的事务数据,因为读取的是无效数据,这个情况被称之为“脏读”,通过调整隔离级别为“可重复读”就可以解决该问题。还有种情况是事务读取了某个范围的数据记录时,其他事务又在这个范围内插入或修改了记录,之前的事务再次查询这个范围的数据记录时,就会产生幻读的问题,MySQL 通过 MVCC 来解决了幻读的问题,也就是通过创建时间的事务版本号和过期时间的事务版本号来判断的。

问题四:在“提交读“的事务隔离级别下,因为两次执行同样的查询,可能会得到不一样的结果,为什么?

问题五:为什么大多数数据库系统的默认级别是“提交读”,而 MySQL 的默认级别是“可重复读”?

问题六:为什么 MySQL 使用 InnoDB 来作为默认的存储引擎?
数据存储的可靠性以及数据的完整性是存储问题的重要难题,InnoDB 引擎支持事务,性能和自动崩溃恢复特性也在众多环境下得到良好的验证。使用 MVCC 来支持高并发,通过间隙锁策略来解决幻读的问题。通过聚簇索引存储数据结构来提高查询的效率和性能。InnoDB 的内部也做了很多的优化来提高插入性能。