一条 SQL 查询语句是如何运行?

你好,我是猿java。

作为一名 Java后端程序员,MySQL应该是接触最多的数据库之一,增删改查更 MySQL数据库的常规操作。 那么,一条 SQL语句在执行的过程中经历了哪些流程呢?它是如何被 MySQL执行的?这篇文章,我们将详细地分析。

为了更好的理解,我们先来看一下 MySQL的架构。

MySQL架构示意图

MySQL是典型的C/S架构,SQL整个执行流程包括:客户端,Server层和存储引擎层三部分。

img.png

img.png

C/S架构,C是指 Client 客户端,S是指 Server 服务端。

模块分析

1. 客户端

客户端是指连接使用MySQL的终端。常见的MySQL客户端有:java代码,这个是java程序员使用最多的,比如mybatis ORM框架;navicat工具,功能强大,能够可视化操作很多种数据库;
mysql-cli,这个是MySQL官方自带的客户端;还有一些网页版的客户端。

2. Server层

Server层是MySQL的核心模块,Server层包含 连接器、查询缓存、分析器、优化器、执行器等核心组件,
涵盖了MySQL大多数核心服务以及所有的内置函数,诸如 存储过程、触发器、视图等所有跨存储引擎的功能也都在Server层实现。下面将分别讲解几个核心组件。

连接器

连接器的主要功能是连接管理和权限校验。当客户端请求过来时,首先是和Server层的连接器交互。

下面通过一个实例来讲解连接层的功能,比如:mysql-cli客户端连接MySQL Server的命令

1
mysql> mysql -h 127.0.0.1 -P 3306 -uroot -p

整个过程分解为:

输入指令,点击 Enter键后会完成经典的TCP 3次握手,客户端和MySQL Server建立TCP连接。

连接建立后,连接器开始对请求进行权限校验,如果Server层配置需要密码校验,会提醒用户输入密码,密码正确进入下一步,密码错误提醒”Access denied for user”;
如果Server层配置不需要密码校验,则直接进入下一步

权限验证成功后,连接器会从权限表把当前用户的所有权限查询并缓存起来,权限缓存的生命周期一直到该连接关闭。

连接器会把权限缓存,因此,只要该连接一直存在就会使用缓存中的权限,这就意味着,即便服务器更改了该用户的权限,只要是在权限更改前还存活的连接,新的权限不生效。这也能很好的解释,
有时候服务端修改了权限配置,客户端不能及时生效。

查询缓存

缓存是 MySQL为了加速查询而设置的,当请求鉴权完成之后,就会到执行缓存查询(Server层开启了缓存),如果命中缓存,则直接返回,否则进入下一步。不过根据小编剧这么多年的工作经验,
缓存使用的场景比较少,比如:MySQL中存放的是一些静态数据或者变更频率特别低,其他的场景这个功能就比较鸡肋了,怎么鸡肋呢?

因为只要对表有更新操作,查询缓存就会失效,如果表的更新和查询操作比较频繁,那么缓存就会一直处于建立和失效的频繁交替中,最终导致查询性能不但没有提升还无形中多维护了缓存。

因此实际生产中,Server层都会设置 query_cache_type=DEMAND,这样SQL默认不会使用查询缓存。如果有特殊需求一定要使用查询缓存,可以显示指定SQL_CACHE,比如下面的SQL语句:

1
mysql> select SQL_CACHE * from user where id = ?;

分析器

分析器,顾名思义就是SQL语句进行分析,那么,分析器对SQL会做哪些分析呢?通常来说有:词法分析 和 语法分析 两种。

词法分析 是判断SQL里面的字符串进行拆解,识别当前SQL是什么操作,SQL里面包含多少字符串,空格等等,比如:下面的sql语句,
词法分析器可以根据 select来判断当前SQL是查询操作,id 为需要查询的结果,where 后面的条件等等;

1
mysql> select id from user where name = 'zhangsan';

语法分析就是检查SQL的语法是否正确,比如下面的SQL语句,把update 错误的写成了 updater,因此语法分析器就能识别该SQL有语法错误,抛出语法错误相关的异常。

1
mysql> updater user set update_time = now() where id = 10;

优化器

优化器目的是对SQL语句进行优化处理。因为SQL语句的编写者能力不一样,编写出来的SQL语句性能也不一样。
Server层如果完全按照SQL语句顺序执行,可能会造成性能问题, 所以需要优化,判断语句能否使用索引等。比如下面的场景:

假如:5000万数据的user表中原存在一个组合索引是index_name_age(name,age),某工程师在没有查看现有索引的情况写编写了如下的SQL语句:

1
mysql> select * from user where age = 30 and name like '张%';

假如 MySQL server层完全按照SQl语句的顺序执行,则该SQL语句不会使用索引,必定会成为慢sql。而有了优化器,语句就可以优化成下面的形式,完全使用上现有的index_name_age(name,age)索引。
这下可以是不是看出了优化器的好处。

1
mysql> select * from user where name like '张%' and age = 30;

执行器

执行器就是运行SQL语句。不过,此处执行器不会在Server层直接执行SQL语句,而是根据数据表中执行引擎类型调用对应的存储引擎提的接口。至于,为什么执行引擎不亲自执行SQL语句,我们会后期进行分享。
不过MySQL此处的设计符合了SOLID软件设计原则 的依赖倒置原则。

3. 存储引擎层

存储引擎层负责数据的存储和提取。采用插件式的架构模式,常见的存储引擎有 InnoDB、MyISAM、Memory等。其中MyISAM是MySQL官方自带的引擎,
但是因为该引擎不支持事务,使得能够支持事务的InnoDB存储引擎得以快速发展,并在MySQL 5.5.5版本夺嫡成功,成为了默认存储引擎。

因此,作为开发,在进入新公司后,最好是要弄清楚公司的MySQL版本以及默认引擎,这样可以避免很多不必要的坑。查看指令如下:

1
2
3
4
5
# 查看数据库版本
mysql> status;

# 查看默认引擎
mysql> SHOW VARIABLES LIKE 'default_storage_engine%';

总结

本文,我们详细分析了一条 SQL查询语句在 MySQL中的全部执行流程。通过了解这些流程,可以帮助我们更好的理解 MySQL的内部结构和原理,以及 vvvMySQL的优化原理。:

  • SQL执行会经历客户端、Server层、存储引擎层 3个部分。
  • Server层包含 连接器、查询缓存、分析器、优化器、执行器等核心组件。
  • 连接器主要职责是管理连接,权限校验
  • 查询缓存主要职责是为查询提供缓存
  • 分析器主要职责是词法分析和语法分析,目的是识别SQL是做什么,有没有语法错误。
  • 优化器主要职责是关注SQL的性能,优化SQL语句怎么更好的去执行,比如:匹配索引,优化join查询的连接顺序。
  • 执行器主要职责是调用存储引擎调的接口和返回结果。
  • 存储引擎主要职责是数据的存储和提取,给执行器提供接口。

学习交流

如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。

drawing