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

1,355 阅读9分钟

总体架构

先看下总体架构,有一个全览的概念。 image.png

连接

MySQL服务监听的端口默认是3306,客户端连接的方式也有很多。可以是同步的也可以是异步的,可以是长连接也可以是短连接,可以是TCP连接也可以是Unix Socket,MySQL有专门处理连接的模块,连接的时候需要验证权限。

客户端每产生一个连接或者一个会话,在服务端就会创建一个线程来处理。反过来,如果要杀死会话的话,就是kill掉线程。 我们用show processlist看下有哪些连接,kill 的时候就是 kill 下面的 id。

image.png

如果客户端太久没动静,连接器会把那些长时间不活动的sleep连接断开,有两个参数wait_timeout和interactive_timeout,默认都是28800,即8小时。

image.png

如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query。这时候如果你要继续,就需要重连,然后再执行请求了。

既然连接消耗资源,会创建线程,那也不能让你无限制的连,所以有个参数控制你的最大连接数,在5.7版本中默认是151个,最大可以设置成100000。

image.png

查询缓存

MySQL内部自带了一个缓存模块,我们看下缓存默认是关闭着的。

image.png

那既然有这个模块干嘛要关着呢,主要是因为MySQL自带的缓存的应用场景有限,第一是它要求SQL语句必须一模一样,中间多一个空格,字母大小写不同都被认为是不同的SQL。
第二是表里面任何一条数据发生变化的时候,这张表所有缓存都会失效,所以对于有大量数据更新的应用,也不适合。
所以缓存这一块,我们还是交给ORM框架,比如MyBatis默认开启了一级缓存,或者独立的缓存服务器,比如Redis。在MySQL8.0开始,查询缓存功能已经被移除了。

解析器和预处理器

解析器Parser和预处理器Preprocessor,这一步主要对SQL语句进行词法和语法分析和语义的解析。

词法分析

词法分析就是你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。

select name from user_innodb where id=1

比如这句sql会被打破成8个单词。

语法分析

第二步就是语法分析,语法分析会对SQL做一些语法检查,比如单引号有没有闭合,然后根据MySQL定义的语法规则,根据SQL语句生成一个数据结构。这个数据结构我们把它叫做解析树(select_lex)

如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句 select 少打了开头的字母“s”。

image.png

预处理器

如果词法和语法都正确,但是字段名或者表名不存在,这时候会在哪里报错,对,就是预处理器。

image.png

预处理器进一步检查解析树的合法。比如: 数据表和数据列是否存在, 别名是否有歧义等。如果通过则生成新的解析树,再提交给优化器。

优化器

在开始执行之前,还要先经过优化器的处理。一条SQL语句是可以有很多执行方式的,最终返回相同的结果,他们是等价的。查询优化器的目的就是根据解析树生成不同的执行计划,然后选择一种最优的执行计划,MySQL里面使用的是基于开销(cost)的优化器,哪种执行计划开销最小,就用哪个。

优化器会优化一些比如当我们对多张表关联查询的时候,以哪个表的数据为基准表;当有多个索引可以使用的时候,选择哪个索引。 但是优化器也不是万能的,并不是再垃圾的SQL语句都能自动优化,也不是每次都能选择到最优的执行计划,大家在编写SQL的时候还是要注意。

优化器完成以后,会把解析树变成一个查询执行计划,查询执行计划是一个数据结构。我们在SQL语句前面加上explain就可以看到执行计划信息。

explain select * from user_innodb where id=1;

如果要看到详细的信息,还可以用format=json,或者开启optimizer trace。

explain format=json select * from user_innodb where id=1;

image.png

执行器

执行器开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误。

如果有权限,就打开表继续执行。执行器使用执行计划去操作存储引擎。它利用存储引擎提供的相应的API来完成操作。最后把数据返回给客户端。

存储引擎

存储引擎有很多,在navicat里面可以看到

image.png

创建表的时候也可以指定,现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。当你执行 create table 建表的时候,如果不指定引擎类型,默认使用的就是InnoDB。

image.png

查看存放的目录

image.png

不同的存储引擎存放数据的文件也不一样,frm是表结构文件,数据文件innodb是一个,memory没有,myisam是两个。

image.png

常用存储类型比较,dev.mysql.com/doc/refman/… image.png

MyISAM(3个文件)

应用范围比较小,表级别锁限制了读/写的性能,因此在web和数据仓库配置中,它通常用于只读或者以读为主的工作。

  • 支持表级别的锁,插入和更新会锁表。不支持事务。
  • 拥有较高的插入insert和查询select的速度。
  • 存储了表的行数(count速度更快)。

如何快速向数据库插入100万条数据,我们可以先以MySIAM引擎插入数据,然后再将存储引擎修改为InnoDB。

InnoDB(2个文件)

mysql5.7里面默认的存储引擎。InnoDB是一个事务安全(与ACID兼容)的MySQL存储引擎,它具有提交、回滚和崩溃恢复功能来保护用户数据。 InnoDB行级锁(不升级为更粗粒度的锁)和Oracle风格的一致非锁读提高了多用户并发性和性能。InnoDB将用户数据存储在聚集索引中,以减少基于主键的常见查询的I/O。为了保持数据完整性,InnoDB还支持外键引用完整性约束。

  • 支持事务,支持外键,因此数据的完整性、一致性更高。
  • 支持行锁和表锁
  • 支持读写并发,写不阻塞读(MVCC)
  • 特殊的索引存放方式,可以减少IO,提升查询效率

适合经常更新的表,存放并发读写或者有事务处理的业务系统。

Memory(1个文件)

将所有数据存储在内存中,以便在需要快速查找非关键数据的环境中快速访问。这个引擎之前被成为堆引擎,其使用案例正在减少;InnoDB及其缓冲池内存区域提供了一种通用、持久的方法来将大部分或者所有数据保存在内存中,而ndbcluster为大型分布式数据集提供了快速的键值查找。

把数据放在内存中,读写的速度很快,但是数据库重启或者崩溃,数据会全部消失,只适合做临时表。

CSV(3个文件)

它的表实际上是带有逗号分隔值的文本文件。csv表允许以csv格式导入或者转储数据,以便与读写相同格式的脚本和应用程序交换数据。因为csv表没有索引,所以通常在正常操作期间将数据保存在innodb表中,并且只在导入或者导出阶段使用csv表。

不允许空行,不支持索引。格式通用,可以直接编辑,适合在不同数据库之间导入导出。

Archive(2个文件)

这些紧凑的未索引的表用于存储和检索大量很少引用的历史、存档或者安全审计信息。

不支持索引,不支持update、delete。

如何选择存储引擎

存储引擎有很多,不同的应用场景下选择不同的存储引擎。

  • 如果对数据一致性要求比较高,需要事务支持,可以选择InnoDB。
  • 如果数据查询多更新少,对查询性能要求比较高,可以选择MySIAM。
  • 如果需要一个用于查询的临时表,可以选择Memory。

如果这些存储引擎不能满足你的需求,没关系,你还能自己开发一个存储引擎,dev.mysql.com/doc/interna… ,按照这个开发规范,实现相应的接口,给执行器操作。之所以执行器能够执行所有的存储引擎,就是存储引擎都遵循了一套规范,提供了相同的操作接口。

MySQL体系总结

总体上我们可以把MySQL分成三层。 image.png

连接层

我们的客户端要连接到MySQL服务器3306端口,必须要跟服务端建立连接,那么管理所有的连接,验证客户端的身份和权限,这些功能就在连接层完成。

服务层

连接层会把SQL语句交给服务层,这里面又包含一系列的流程:

比如查询缓存的判断、根据SQL调用相应的接口,对我们的SQL语句进行词法和语法的解析(比如关键字怎么识别,别名怎么识别,语法有没有错误等等)。

然后就是优化器,MySQL底层会根据一定的规则对我们的SQL语句进行优化,最后再交给执行器去执行。

存储引擎

存储引擎就是我们的数据真正存放的地方,在MySQL里面支持不同的存储引擎,再往下就是内存或者磁盘。

最后感谢大家的观看~