6.1 SQL执行过程
MySQL架构分为两层:server层和存储引擎层
- Server层负责建立连接、分析和执行SQL
- MySQL大多数核心功能模块都在这里:连接器、查询缓存、解析器、预处理器、优化器、执行器等
- 还有所有的内置函数
- 所有跨存储引擎的功能
- 存储引擎层负责数据的存储和提取
- 支持InnoDB、MyISAM、Memory等多个存储引擎
6.1.1 连接器
MySQL是基于TCP协议进行传输的,所以在连接MySQL的时候需要先进行TCP三次握手,在命令行使用命令进行连接
mysql -h $ip -u$user -p
用户通过用户密码成功连接后,连接器会获取用户的权限,然后保存起来,在后续的此连接的任何操作,都会基于连接开始的时候读取到的权限逻辑进行判断
建立连接后,即使修改了该用户的权限,也不影响已连接的权限。只有新建的连接才会有新的权限设置
6.1.1.1 查看MySQL服务的客户端连接
可以执行show processlist
命令进行查看
6.1.1.2 空闲连接会一直占着
不会,MySQL定义了空闲连接的最大空闲时长,由wait_timeout
参数控制,默认值是8小时,超过这个时间,连接器就会把这个连接断开
使用命令可以查看该值
show variables like 'wait_timeout';
可以手动断开空闲的连接,使用的是
kill connection + id
当空闲的连接被服务端主动断开后,这个客户端并不会马上知道,等到客户端在发起下一个请求时,才会收到报错
“ERROR 2013 (HY000): Lost connection to MySQL server during query”
6.1.1.3 MySQL的连接限制
MySQL服务支持的最大连接数由max_connections
参数控制
show variables like 'max_connections';
MySQL的连接跟HTTP一样,有短连接和长连接的概念
// 短连接
连接 mysql 服务(TCP 三次握手)
执行sql
断开 mysql 服务(TCP 四次挥手)
// 长连接
连接 mysql 服务(TCP 三次握手)
执行sql
执行sql
执行sql
....
断开 mysql 服务(TCP 四次挥手)
一般推荐长连接,但是使用长连接可能会占用内存增多,因为_MySQL在执行查询过程中临时使用内存管理连接对象__,_只有在连接断开的时候才会释放
6.1.1.4 怎么解决长连接占用内存的问题
两个解决方案:
定期断开长连接
客户端主动重置连接
MySQL在5.7版本实现了
mysql_reset_connection()
函数的接口,可以使得客户端执行一个很大的操作后,在代码里调用该函数,来进行重置连接,达到释放内存的效果
与客户端进行 TCP 三次握手建立连接;
校验客户端的用户名和密码,如果用户名或密码不对,则会报错;
如果用户名和密码都对了,会读取该用户的权限,然后后面的权限逻辑判断都基于此时读取到的权限;
6.1.2 查询缓存
连接器完成连接后,服务端收到SQL语句,就会解析出SQL语句是什么类型的语句
如果是SELECT语句,MYSQL会先去查询缓存(Query Cache)查找缓存数据,这个查询缓存是以key-value
形式保存在内存中,key为SQL查询语句,value为SQL语句查询的结果
如果缓存命中,就会直接发送value给客户端,否则就继续往下执行
在MySQL8.0版本,这个查询缓存被删除了,因为这个查询缓存的命中率很低,因为只要有一个表有更新操作,那么这个表的查询缓存就会被清空,如果刚缓存了一个查询结果很大的数据,还没有使用,刚好这个表有更新操作,查询缓存就被清空了,相当于缓存浪费了
这里说的查询缓存是 server 层的,也就是 MySQL 8.0 版本移除的是 server 层的查询缓存,并不是 Innodb 存储引擎中的 buffer pool
6.1.3 解析SQL
解析器会做下面两件事
词法解析:识别关键字
语法解析:根据词法解析的结果,根据语法规则,构建出SQL语法树
如果我们输入的SQL语句语法不对,就会在解析器这个阶段报错
注意:表不存在或者字段不存在,并不在解析器里识别。解析器只负责检查语法和构建语法树,但不会去查表或者字段存不存在
6.1.4 执行SQL
经过解析器后,进入执行SQL查询语法的流程,主要可分为下面三个阶段
prepare阶段,预处理阶段
optimize阶段,优化阶段
execute阶段,执行阶段
6.1.4.1 预处理器
预处理阶段做以下的事:
检查SQL查询语句中的表或者字段是否存在
将
*
扩展为表上所有列
6.1.4.2 优化器
预处理阶段后,需要为SQL语句制定一个执行计划,就交由优化器完成
优化器主要负责将SQL查询语句的执行方案确定下来,决定使用哪个索引
在查询语句前加个explain
命令,就会输出这条SQL语句的执行计划
6.1.4.3 执行器
执行器:开始真正执行语句。在执行过程中,执行器就会和存储引擎交互,过程如下
主键索引查询
全表扫描
索引下推
6.1.4.3.1 主键索引查询
在SQL语句中查询条件使用主键索引,访问类型为const,那么执行器与存储引擎执行流程大致如下
执行器第一次查询,调用
read_first_record
函数指针指向函数,访问类型为const,指向InnoDB引擎索引查询的接口,让存储引擎定位符合条件的记录存储引擎通过主键索引的B+树结构定位到符合条件的记录,如果记录不存在,就会向执行器上报记录找不到的错误,查询结束;如果记录存在,则返回记录给执行器
执行器从存储引擎读到记录后,接着判断记录是否符合查询条件,如果符合则发送给客户端,不符合则跳过该记录
执行器查询的过程是一个while循环,所以会在查询一次,此时调用
read_record
函数指针指向的函数,因为优化器选择的访问类型是const,这个函数指针指向一个永远返回-1的函数,所以当调用函数的时候,执行器退出循环,查询结束
6.1.4.3.2 全表扫描
全表查询是没有用到索引,所以优化器决定选用访问类型为ALL
执行器第一次查询,调用
read_first_record
函数指针指向函数,访问类型为const,指向InnoDB引擎全扫描的接口,让存储引擎定位符合条件的记录执行器会判断读到的记录是不是符合条件,不是则跳过;是则将记录发送给客户(Server 层每从存储引擎读到一条记录就会发送给客户端,之所以客户端显示的时候是直接显示所有记录的,是因为客户端是等查询语句查询完成后,才会显示出所有的记录)
执行器查询的过程是一个 while 循环,所以还会再查一次,会调用 read_record 函数指针指向的函数,因为优化器选择的访问类型为 all,read_record 函数指针指向的还是 InnoDB 引擎全扫描的接口,所以接着向存储引擎层要求继续读刚才那条记录的下一条记录,存储引擎把下一条记录取出后就将其返回给执行器(Server层),执行器继续判断条件,不符合查询条件即跳过该记录,否则发送到客户端;
一直重复上述过程,直到存储引擎把表中的所有记录读完,然后向执行器(Server层) 返回了读取完毕的信息;
执行器收到存储引擎报告的查询完毕的信息,退出循环,停止查询。
6.1.4.3.3 索引下推
索引下推能够减少二级索引在查询时的回表操作,提高查询的效率(它是将server层部分负责的事,交由存储引擎层去处理)
总结
执行一条 SQL 查询语句,期间发生了什么?
连接器:建立连接,管理连接、校验用户身份;
查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。MySQL 8.0 已删除该模块;
解析 SQL,通过解析器对 SQL 查询语句进行词法分析、语法分析,然后构建语法树,方便后续模块读取表名、字段、语句类型;
执行 SQL:执行 SQL 共有三个阶段:
预处理阶段:检查表或字段是否存在;将
select *
中的*
符号扩展为表上的所有列。优化阶段:基于查询成本的考虑, 选择查询成本最小的执行计划;
执行阶段:根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端;
6.2 MySQL存储一行记录
总结
- MySQL的NULL值是怎么存放的?
MySQL的Compact行格式中会使用NULL值列表来标记NULL的列,NULL值并不会存储在行格式中的真实数据
NULL值列表会占用1字节空间,当表中所有字段都定义成NOT NULL,行格式就不会有NULL值列表,可以节省1字节空间