一条SQL语句在MySql中的执行流程

312 阅读7分钟

我们在客户端编写一条SQL语句,在执行后会返回相应的数据,那么这条语句的执行流程是怎样的呢?Mysql底层是如何实现的?

下面是一条查询SQL

SELECT * FROM USER WHERE ID = 1

我们要搞懂这条SQL的整个执行流程,首先我们需要对MySql的基本架构要有一定的了解,基本架构图大致如下:

截屏2022-08-30 16.46.24.png
  • 连接器

客户端执行SQL时首先需要与客户端建立连接,连接建立完成之后可以通过命令 SHOW PROCESSLIST查看和Server端建立的连接,Command为当前连接的状态,如果长时间不进行数据的交互,将会自动断开连接,默认断开连接的时间时8h,如果大量的状态为Sleep的连接堆积在服务端,将会极大的消耗Server的内存,所以说对于Mysql配置wait-timeout还是非常有必要的

截屏2022-08-30 16.54.52.png

Linux可以配置my.cnf文件,找到wait-timeout和interactive_timeout做合适自己的超时配置即可,只配置wait-timout是不生效的

建立连接的过程比较复杂,所以应该尽量避免连接的重复建立,推荐使用长连接,但是长连接的大量使用也会存在一些内存消耗的问题,如果连接量太大不做处理将可能会引发OOM,所以我们有一下两种常用的解决方案:

  1. 定期断开连接,或者对于操作比较耗时的长连接,执行完成之后,主动断开连接,释放资源缓解服务端的压力
  2. 对于执行比较大的操作,执行完成之后,可以通过命令mysql_reset_connection重新初始化连接资源,这个过程不需要重新连接,只是连接变成刚刚连接之后的状态
  • 查询缓存

我们都是到缓存一旦命中,效率还是非常高的,就不需要执行下面一系列复杂的流程去引擎中获取数据了,Mysql缓存的结构通常为K-V结构的,key为sql语句,value为这条sql返回的结果。 但是在大多数情况下是不建议开启缓存的,为什么会这样呢?

如果在某一张表上建立的查询缓存,如果这张表涉及的更新操作比较频繁,每一次更新操作都会将对应的查询缓存进行清空,显然是对服务层的压力比较大的,因此对于更新比较频繁的表,查询缓存是弊大于利的,但是如果对于一些配置表,更新不是特别频繁,查询请求比较大,可以根据自己的业务去设置查询缓存,同时Mysql中也提供了自定义缓存设置机制,如果将query_cache_type设置成DEMAND在进行查询时,在查询语句中添加SQL_CACHE将会对这条SQL进行缓存处理。

SELECT SQL_CACHE * FROM USER WHERE ID = 1

Mysql8.0版本开始不支持缓存查询机制了,这块已经废弃掉。。。。。

  • 分析器

SQL语句执行到此处,说明没有命中缓存查询,在分析器这一层,需要对我们从客户端传过来的空格和字符串进行拼接的SQL语句进行词法和语法的校验,首先会按照结构对SELECT * FROM USER WHERE ID = 1进行拆分 SELECT 表示需要进行查询, * 需要查询的字段信息,FROM之后的表示需要查询的表,WHERE后面所跟的条件,按照对应表中的字段进行过滤查询,关键词法没有问题之后会对按照Mysql的语法进行校验,如果在分析器这一层有问题往往会报 1064 - You have an error in your SQL syntax; 定位关键信息在‘use near’之后

  • 优化器

经过分析器之后,SQL语句基本上已经没有问题了,可以执行数据的获取操作,如下语句:

SELECT * FROM T1 JOIN T2 USING(name) WHERE T1.c = 'x' AND T2.d = 'y'

执行这条语句时我们可以先到T1表中获取c这个字段值为x的数据,然后再去T2表中进行关联查询这两种查询方式效率是明显不一样的,优化器会帮助我们选择最优的执行计划,最终交给执行器进行执行。

  • 执行器

执行器会根据优化之后的SQL方案进行执行,执行器的执行是调用存储引擎接口去获取数据的,在操作表时,首先会判断当前用户时候有操作表的权限,如果没有权限,将会报异常。

具体执行时会调用引擎接口,从第一行进行扫描比对ID是否为1,如果不为1,则继续往下走,一行行的重复进行,调用索引的表执行流程也是大致如此的。

在慢查询日志中的rows_examined字段表示本次查询扫描了多少行数据,但是执行器执行一次不代表扫描一行,有时也会扫描多行,因此执行器的执行次数和rows_examined往往是不一样的 不要进行混淆

好了 上面我们介绍了从查询维度的sql执行流程以及mysql的基础架构各部分的作用,那么更新操作和查询操作有什么区别呢?

生产环境的mysql通常恢复到半个月之内的任意时刻点的数据,那么这些数据快照是如何进行保存的呢? 数据出现问题是如何进行回滚操作的?

UPDATE USER SET user_name = 'Tom' WHERE ID = 10;

其实更新操作的执行流程会将查询的流程走一遍,词法分析器和语法分析器会区分出这个操作是一个更新操作,优化器会通过索引ID对本条数据进行更新操作,但是更新操作会涉及到写日志的操作,redo log(重做日志) binlog(归档日志)

下面我们着重分析redolog和binlog:

redo log : 我们在进行数据的更新操作时,为了兼顾性能,并不是每次更新操作都直接去磁盘中找到对应的数据进行写操作的,每次更新每次IO,是不合理的,InnoDB引擎会将变更数据写入到redolog中,然后更新内存数据,到此完成了数据的更新,InnoDB会在资源空闲的情况下将redolog日志中的数据进行落盘操作。

redolog是如何写的呢?

InnoDB的redolog大小是固定的,但是可以配置一组四个文件,每个文件的大小为1GB 那么这个文件组可以存储4GB大小的日志文件,从文件的头开始写,写到尾部再从头部开始继续写,写的过程中有write-pos指针,check-point指针也是不断往后推移的,check-point移动过去的位置日志将会进行擦除,write-pos和check-point之间的位置为空白位置,也就是文件的可用位置,如果pos追赶上了check-point,此时不能继续进行写操作,只能在此阻塞住,等待point的擦除。 有了redo-log的保证,mysql在进行宕机时,之前保存的数据就不会丢失,这种机制我们称之为crash-safe

bin-log:

mysql为什么要设计两个日志模块? 我们从Mysql的整体架构来看大体可以分成两部分Server和引擎部分,InnoDB引擎有redo-log的加持能够有crash-safe能力,Server层独有的日志就是binlog,binlog也不具备crash-safe的能力,只能用于归档,之前Mysql默认的引擎是MyISAM引擎,这个引擎时没有crash-safe能力的,因此Server层的binlog归档日志+引擎层的redolog日志能够保证Mysql数据的安全

两种日志的几个不同点: binlog是Server层的日志, redolog是引擎层的日志。 binlog是追加写的形式,一个文件写完了可以接着写另一个文件,但是redolog是固定大小的,写操作是循环的。 redolog是物理日志,记录的是页数据的修改,而binlog是记录的逻辑操作。

截屏2022-10-17 16.52.13.png

最终执行commit之后redolog的状态由prepare状态变为commit状态,这就是redolog的两阶段提交。

两阶段提交的意义: 为了保证redolog和binlog两份日志中的逻辑的一致性。

redo log用于保证crash-safe能力。
innodb_flush_log_at_trx_commit这个参数设置成1的时候,表示每次事务的redo log都直接持久化到磁盘。
这个参数建议设置成1,这样可以保证MySQL异常重启之后数据不丢失。
SET  GLOBAL innodb_flush_log_at_trx_commit = 1