前言
当我们用 navicat、mysql workbench 等mysql 的客户端执行一条sql语句后,我们就能得到相应的结果。例如:
那么这个过程发生了什么呢?
执行一条sql 就是一次Rpc的调用
mysql 是一个客户端、服务端的架构。我们平时使用的大部分程序app其实是由两部分组成的,一部分是客户端程序,一部分是服务器程序。
以我们常用的微信、qq 为例。我们手机里面装的客户端,机房的服务器中运行着server端。我们平时发信息都是其实都是用客户端和服务端打交道。比如你和你女朋友发信息的过程:
消息被客户端包装了一下,添加了发送者和接收者信息,然后从你的微信客户端传送给微信服务器;
微信服务器从消息里获取到它的发送者和接收者,根据消息的接收者信息把这条消息送达到你女朋友的微信客户端,你女朋友的微信客户端里就显示出你给他发了一条消息。
mysql的使用过程跟这个是一样的,它的服务器程序直接和我们存储的数据打交道,然后可以有好多客户端程序连接到这个服务器程序,发送增删改查的请求,然后服务器就响应这些请求,从而操作它维护的数据。
主要流程如下:
所以,一条sql的执行,就是一次rpc的调用。后面有时间也会分享RPC 相关的东西,一起交流学习!
服务器怎么处理客户端请求
不管我们用了哪种客户端和服务器进程是采用哪种方式进行通信,最后实现的效果都是:客户端进程向服务器进程发送一段文本(MySQL语句),服务器进程处理后再向客户端进程发送一段文本(处理结果)。主要过程如下:
从图中我们可以看出,服务器程序处理来自客户端的查询请求大致需要经过三个部分,分别是 连接管理、解析优化与执行、存储引擎三个部分。其中连接管理、解析优化与执行常常被分为mysql的 server 层。
连接管理
客户端进程可以采用TCP/IP、命名管道或共享内存、Unix域套接字这几种方式之一来与服务器进程建立连接。
对连接的管理也使用了池化技术:
每当有一个客户端进程连接到服务器进程时,服务器进程都会创建一个线程来专门处理与这个客户端的交互,当该客户端退出时会与服务器断开连接,服务器并不会立即把与该客户端交互的线程销毁掉,而是把它缓存起来,在另一个新的客户端再进行连接时,把这个缓存的线程分配给该新客户端。这样就起到了不频繁创建和销毁线程的效果,从而节省开销。
查询缓存
由于表经常更新,查询缓存的失效频繁,查询缓存往往利大于弊。,MySQL 8.0 版本开始直接将查询缓存的整块功能删掉了。
语法解析
如果查询缓存没有命中,接下来就需要进入正式的查询阶段了。因为客户端程序发送过来的请求只是一段文本而已,所以MySQL服务器程序首先要对这段文本做分析,判断请求的语法是否正确,然后从文本中将要查询的表、各种查询条件都提取出来放到MySQL服务器内部使用的一些数据结构上来。
查询优化
经过了语法解析,MySQL 就知道你要做什么了。在开始执行之前,还要先经过查询优化的处理。优化处理是指在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。我们可以使用EXPLAIN语句来查看某个语句的执行计划 。
大部分优化的逻辑是基于成本的优化
在MySQL中一条查询语句的执行成本是由两个方面组成的 :
I/O成本 :从磁盘到内存这个加载的过程损耗的时间称之为I/O成本。
CPU成本 :读取以及检测记录是否满足对应的搜索条件、对结果集进行排序等这些操作损耗的时间称之为CPU成本。
对于InnoDB存储引擎来说 ,mysql规定读取一个页面花费的成本默认是1.0,读取以及检测一条记录是否符合搜索条件的成本默认是0.2 。
拿单表查询来举例,成本计算步骤如下:
根据搜索条件,找出所有可能使用的索引
计算全表扫描的代价
计算使用不同索引执行查询的代价
对比各种执行方案的代价,找出成本最低的那一个。
存储引擎
截止到服务器程序完成了查询优化为止,还没有真正的去访问真实的数据表,MySQL服务器把数据的存储和提取操作都封装到了一个叫存储引擎的模块里。我们知道表是由一行一行的记录组成的,但这只是一个逻辑上的概念,物理上如何表示记录,怎么从表中读取数据,怎么把数据写入具体的物理存储器上,这都是存储引擎负责的事情。为了实现不同的功能,MySQL提供了各式各样的存储引擎,不同存储引擎管理的表具体的存储结构可能不同,采用的存取算法也可能不同。
为了管理方便,所以大部分人把连接管理、查询缓存、语法解析、查询优化这些并不涉及真实数据存储的功能划分为MySQL server的功能,
把真实存取数据的功能划分为存储引擎的功能。
执行器
执行器就是各种不同的存储引擎向上边的MySQL server层提供统一的调用接口(也就是存储引擎API),包含了几十个底层函数,像"读取索引第一条内容"、"读取索引下一条内容"、"插入记录"等等。
比如执行一条查询sql 时,开始执行的时候,要先判断一下你对这个表 有没有执行查询的权限,如果没有,就会返回没有权限的错误;
拿我们开头的例子中,id字段没有索引,那么执行器的执行流程是这样的:
调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。
但是对于 插入、删除和修改的sql语句,还要涉及到redolog、undolog 和binlog 的操作。这个我们有空再聊。
所以只需按照生成的执行计划调用底层存储引擎提供的API(执行器),获取到数据后返回给客户端就好了。一条sql 语句就执行完成了。