2.1.1. 一条SELECT语句的执行过程
MySQL 的架构共分为两层:Server 层和存储引擎层
- Server层负责建立连接、分析和执行SQL
- 存储引擎层负责数据的存储和提取,支持 InnoDB、MyISAM、Memory 等多个存储引擎,MySQL5.5以后默认使用InnoDB,默认索引类型是B+树。
2.1.1.1. 连接器:
MySQL基于TCP协议传输需要经过三次握手,连接后开始验证用户名和密码,通过后获取用户权限。管理员中途修改用户权限不会影响已存在连接的权限,新建立的连接使用修改后的权限。空闲连接超过8小时后会自动断开, kill connection + id命令可以手动断开空闲连接,MySQL支持的最大连接数是151个。
MySQL也有短连接接和长连接的概念
-
- 短连接:连接->执行->断开 ......
- 长连接:连接->执行->执行->执行->......->断开
长连接断开之前一直占用内存,累计太多导致内存占用太多,可能会被系统强制杀掉,造成服务异常。
解决长连接占用内存:
-
- 定期断开长连接
- 客户端主动重置连接,MySQL 5.7 版本实现了 mysql_reset_connection() 函数的接口,在执行一个很大的操作后,通过调用mysql_reset_connection() 函数来重置连接,过程不需要重连和权限验证。
2.1.1.2. 查询缓存:
如果是查询操作,先查询缓存,如果缓存命中直接返回结果给客户端,未命中就向后执行。MySQL 8.0 开始删除了server 层的查询缓存,因为命中率较低。
2.1.1.3. 解析SQL:
SQL语句正式执行前,先由解析器做两件事。
- 词法分析:
首先会识别出关键字,例如,SQL语句 select username from userinfo,解析后获得4个token,有两个Keyword分别是select和from。
关键字 | 非关键字 | 关键字 | 非关键字 |
select | username | from | userinfo |
- 语法分析:
根据语法规则判断该SQL语句是否满足,通过后会构建语法树,方便后面模块获取 SQL 类型、表名、字段名、 where 条件等等。
2.1.1.4. 执行SQL
每条SELECT 查询语句流程主要可以分为下面这三个阶段:
-
- prepare 阶段,也就是预处理阶段;
- optimize 阶段,也就是优化阶段;
- execute 阶段,也就是执行阶段;
- 预处理器:
在这里检查查询语句中的表或者字段是否存在,将select * 中的 *,扩展为表中的所有字段。表或字段如果不存在会在这里报错。
- 优化器:
优化器为查询语句指定一个执行计划,例如,表里有多个索引优化器会根据索引基数选择一个较适合的索引。
- 执行器:
执行器是真正执行语句的,执行过程会和存储引擎交互,交互以记录为单位。执行过程有三种:
-
-
- 主键索引查询
- 全表扫描
- 索引下推
-
主键索引查询:
- 执行器第一次查询,调用指向InnoDB引擎索引查询接口的read_first_record 函数指针,然后把条件给存储引擎,来定位符合条件的第一条记录。
- 存储引擎通过主键索引的 B+ 树结构定位到第一条记录,记录存在,将记录返回给执行器。不存在返回错误然后查询结束;
- 执行器从存储引擎读到记录后,接着判断记录是否符合查询条件,符合则发送给客户端,如果不符合则跳过该记录。
- 执行器查询的过程是一个 while 循环,所以还会再查一次,因为不是第一次查询了,会调用 read_record 函数指针指向的函数,如果都查询完了会指向一个永远返回-1的函数,然后退出循环结束查询。
至此,这个语句就执行完成了。
全表扫描:
如果查询语句条件没有使用索引,优化器会决定用全表扫描的方式。
- 执行器第一次查询,调用指向InnoDB引擎全扫描接口的read_first_record函数指针,让存储引擎读取表中的第一条记录。
- 执行器判断这条记录是否符合条件,不符合就跳过这一条,符合就将记录发给客户端。(Server 层每从存储引擎读到一条记录就会发送给客户端,之所以客户端显示的时候是直接显示所有记录的,是因为客户端是等查询语句查询完成后,才会显示出所有的记录)
- 执行器查询的过程是一个 while 循环,所以还会再查一次,接着向存储引擎层要求继续读刚才那条记录的下一条记录,存储引擎把下一条记录取出后就将其返回给执行器(Server层),执行器继续判断条件,不符合查询条件即跳过该记录,否则发送到客户端;
- 一直重复上述过程,直到存储引擎把表中的所有记录读完,然后向执行器(Server层) 返回了读取完毕的信息;
- 执行器收到存储引擎报告的查询完毕的信息,退出循环,停止查询。
至此,这个语句就执行完成了。
索引下推:
索引下推能够减少二级索引(基于非主键字段构建的索引)在查询时的回表操作,提高查询的效率,因为它将 Server 层部分负责的事情,交给存储引擎层去处理了。
总结:使用组合索引时,可能会有部分字段无法使用到索引,这时可以使用索引下推来减少回表操作,索引下推就是将所有查询条件都判断完成后再回表,没有使用索引下推就只判断了使用到索引的字段,其余字段的查询条件还需要回到Server层来判断。
组合索引当遇到范围查询 (>、<) 就会停止匹配,也就是 最左侧的字段能用到联合索引,但是后面字段则无法利用到索引。
不使用索引下推(MySQL 5.6 之前的版本)时,执行器与存储引擎的执行流程是这样的:
- Server 层首先调用存储引擎的接口定位到满足查询条件的第一条二级索引记录,也就是定位到符合最左侧查询条件的第一条记录;
- 存储引擎根据二级索引的 B+ 树快速定位到这条记录后,获取主键值,然后进行回表操作,将完整的记录返回给 Server 层;
- Server 层再判断该记录的后续的条件,如果成立则将其发送给客户端;否则跳过该记录;
- 继续向存储引擎索要下一条记录,存储引擎在二级索引定位到记录后,获取主键值,然后回表操作,将完整的记录返回给 Server 层;
- 如此往复,直到存储引擎把表中的所有记录读完。
没有索引下推的时候,每查询到一条二级索引记录,都要进行回表操作,然后将记录返回给 Server,接着 Server 再判断该记录是否符合后续的查询条件。
而使用索引下推后,判断后续查询条件的工作交给了存储引擎层,过程如下 :
- Server 层首先调用存储引擎的接口定位到满足查询条件的第一条二级索引记录,也就是定位符合最左侧查询条件的第一条记录;
- 存储引擎定位到二级索引后,先不执行回表操作,而是先判断一下该索引中包含的查询条件是否成立。如果条件不成立,则直接跳过该二级索引。如果成立,则执行回表操作,将完成记录返回给 Server 层。
- Server 层再判断其他的查询条件是否成立,如果成立则将其发送给客户端;否则跳过该记录,然后向存储引擎索要下一条记录。
- 如此往复,直到存储引擎把表中的所有记录读完。
如果执行计划里的 Extr 部分显示了 “Using index condition”,说明使用了索引下推。