首先了解下MySQL的内部存储机制,其实我们可以把常用工具MySql workbench当做是客户端,而MySQL是服务器,结合计算机网络里学习的内容,我们可以理解为两个应用程序在通信,也就是局域网内的CS架构。
MySQL架构
客户端依据通信协议请求服务端,而MySQL这个服务器执行SQL语句命令并给出反馈,整体架构如下:
可以粗略的把MySQL服务器分为两层,上面的为Server层,主要包括连接器、查询缓存、分析器【分析器+预处理器】、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块:
- 连接器: 身份认证和权限相关(登录 MySQL 的时候),我们在数据库层执行SQL语句时,应用程序会连接到相应的数据库服务器,把SQL语句发送给服务器处理,实际上也就是通信,数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个
- 查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。在接收到查询请求后,并不会直接去数据库查询,而是在数据库的查询缓存中找是否有相对应的查询数据(某条给定的查询语句在第一次执行时,服务器会缓存这条查询语句和他返回的结果。)
- 如果存在,那么在返回查询结果之前,MySQL会检查一次用户权限。如果权限没有问题,key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被则直接从缓存中拿到结果返回给客户端。
- 查询不会被解析,不用生成执行计划,不会被执行。
- 判断是否命中缓存是将此查询语句和缓存中的查询语句进行比对,如果完全相同,那就认为它们是相同的,就认为命中缓存了。
- 如果不存在,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中
- 分析器: 没有命中缓存的话,SQL 语句就会经过分析器,Mysql通过将SQL语句进行解析,并生成一棵对应的解析树。MySQL解析器将使用MySQL语法分析(语法规则验证)和解析查询,如将验证是否使用错误的关键字,或者关键字的顺序是否正确
- 预处理器:预处理器根据一些MySQL规则进一步检查解析树是否合法,如数据表和数据列是否存在,解析列名和别名,是否有歧义。接下来预处理器会验证用户权限(precheck)。查看用户是否有相应的操作权限
- 优化器: 按照 MySQL 认为最优的方案去执行。例如表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序,将SQL语句转化成执行计划,一条查询可以有很多种执行方式,最后都返回相同的结果,最后找到其中最好的执行计划(Mysql使用基于成本的优化器,它将尝试预测一个查询使用某种执行计划的成本,选择其中成本最小的一个)
- 执行器: Mysql根据执行计划给出的指令逐步执行。开始执行的时候,要先判断一下你对这个表有没有执行查询的权限,如果没有就会返回没有权限的错误。在此过程中,有大量的操作需要通过调用存储引擎实现的接口完成,这些接口即为“handler API”接口。查询中的每一个表由一个handler的实例表示。(实际上,在优化阶段Mysql就为每一个表创建了一个handelr实例,优化器可以根据这些实例的接口获取表的相关信息,如表的所有列名、索引统计信息等)
下面的一层为存储引擎,以及真实存储的数据,存储引擎又分为很多中,在【MySQL数据库基础 五】数据库存储引擎这篇文章里详细的分析过MySQL的存储引擎选择。以上的分析器和预处理器也可以简化为一个分析器:
总体而言MySQL的架构分为两层
- Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
- 存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎
这两层共同构建了MySQL
各环节注意事项
各个环节中可能遇到的问题及使用指南。
连接的权限时效
连接命令中的 mysql 是客户端工具,用来跟服务端建立连接。在完成经典的 TCP 握手后,连接器就要开始认证身份,这个时候用的就是输入的用户名和密码。
- 如果用户名或密码不对,你就会收到一个"Access denied for user"的错误,然后客户端程序结束执行。
- 如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。
一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置
长连接使用指南
建立连接的过程通常是比较复杂的,所以在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。但是全部使用长连接后,有些时候 MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了。可以考虑以下两种方案:
- 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
- 如果用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态
通过以上两种方式就可以实现既不用频繁创建长连接又能保证长连接内存不会超大
不建议使用查询缓存
查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。
好在 MySQL 也提供了这种“按需使用”的方式。你可以将参数 query_cache_type 设置成 DEMAND,这样对于默认的 SQL 语句都不使用查询缓存。而对于你确定要使用查询缓存的语句,可以用 SQL_CACHE 显式指定【减少建立查询缓存耗费的资源】,像下面这个语句一样:mysql> select SQL_CACHE * from T where ID=10;
需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了
查询语句执行流程
了解了MySQL的架构之后呢,正式理解下一条语句如何进行执行和优化。,以如下的查询语句举例分析,从人员库的人员信息表里拿出一条人员数据:
use User go select * from UserInfo where name='tml' and age='26' and sex='男';
分析下这个语句的执行流程:
- 连接器: 连接数据库User,并通过输入账号密码通过连接认证【数据库权限check】
- 查询缓存: 先执行查询缓存,如果命中数据,在返回之前先判断是否有权限【查询缓存check】,如果有则返回,没有则继续向下
- 分析器: 若没有命中缓存,进行语法分析,提取关键字:use 、go、select 、from 、where 、and ,判断关键字是否满足MySQL的语法
- 预处理器:进一步获取UserInfo表名、列名:name、age、sex,判断这些元素是否都存在,如果都存在则验证权限【权限precheck】,如果权限存在继续向下
- 优化器: 判断先获取哪一列,产生各种方案【name->age->sex、name->sex->age、age->sex->name等】,最终会选取最优、成本最小的方案去执行
- 执行器: 执行前先判断是否有权限执行语句【表权限check】, 调用handler查询相关接口,从InnoDB存储引擎中获取数据,
- 调用 InnoDB 引擎接口取这个表的第一行,判断是否满足条件
name='tml' and age='26' and sex='男'
如果不是则跳过,如果是则将这行存在结果集中; - 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
- 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端
对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。以上就是查询的执行流程