6.1 SQL执行过程

https://xiaolincoding.com/mysql/base/how_select.html#mysql-%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84

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 怎么解决长连接占用内存的问题

两个解决方案:

  1. 定期断开长连接

  2. 客户端主动重置连接

      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

解析器会做下面两件事

  1. 词法解析:识别关键字

  2. 语法解析:根据词法解析的结果,根据语法规则,构建出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字节空间

6.? 引擎分类

https://juejin.cn/post/7160557698642083847