一条查询SQL是如何执行的?
1.通讯协议
首先,MySQL 必须要运行一个服务,监听默认的端口(3306)
MySQL 支持多种通信协议。
第一个就是 TCP/IP 协议,编程语言的连接模块都是用 TCP 协议连接到 MySQL 服务器的
[root@iZwz~]# netstat -an | grep 3306
tcp6 0 0 :::33060 :::* LISTEN
tcp6 0 0 :::3306 :::* LISTEN
第二种是 Unix Socket。比如我们在 Linux 服务器,不用通过网络协议,也可以连接到 MySQL 的服务器,它需要用到服务器上的一个物理文件(mysql.sock)值得一提的是,这是所有协议中最高效的一个。
mysql> show variables like 'socket';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| socket | /tmp/mysql.sock |
+---------------+-----------------+
1 row in set (0.01 sec)
另外还有命名管道(Named Pipes)和内存共享(Share Memory)的方式。
Share Memory协议,这个协议一般人不知道,肯定也没用过,因为这个只有windows可以使用,使用这个协议需要在配置文件中在启动的时候使用–shared-memory参数,注意的是,使用此协议,一个host上只能有一个server,所以这个东西一般没啥用的,除非你怀疑其他协议不能正常工作,实际上微软的SQL Sever也支持这个协议
Named Pipes协议,这个协议也是只有windows才可以用,同shared memory一样,使用此协议,一个host上依然只能有一个server,即使是使用不同的端口也不行,Named Pipes 是为局域网而开发的协议。内存的一部分被某个进程用来向另一个进程传递信息,因此一个进程的输出就是另一个进程的输入。第二个进程可以是本地的(与第一个进程位于同一台计算机上),也可以是远程的(位于联网的计算机上)。正因为如此,假如你的环境中没有或者禁用TCP/IP环境,而且是windows服务器,那么好歹你的数据库还能工作。使用这个协议需要在启动的时候添加–enable-named-pipe选项
第二个是通信方式。
MySQL 使用半双工的通信方式。
半双工意味着要么是客户端向服务端发送数据,要么是服务端向客户端发送数据,这两个动作不能同时发生。
所以客户端发送 SQL 语句给服务端的时候,(在一次连接里面)数据是不能分成小块发送的,不管你的 SQL 语句有多大,都是一次性发送。
如果发送给服务器的数据包过大,我们必须要调整 MySQL 服务器配置 max_allowed_packet 参数的值
mysql> show variables like 'max_allowed_packet';
+--------------------+---------+
| Variable_name | Value |
+--------------------+---------+
| max_allowed_packet | 1048576 |
+--------------------+---------+
1 row in set (0.00 sec)
另一方面,对于服务端来说,也是一次性发送所有的数据,不能因为你已经取到了想要的数据就中断操作。
所以,我们一定要在程序里面避免不带 limit 的这种操作。
连接方式
第三个是连接这一块。
MySQL 既支持短连接,也支持长连接。短连接就是操作完毕以后,马上 close 掉。长连接可以保持打开,后续的程序访问的时候还可以使用当前连接。
长时间不活动的连接,MySQL 服务器会断开。
# 非交互式超时时间,如 JDBC 程序
mysql> show global variables like 'wait_timeout';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wait_timeout | 28800 |
+---------------+-------+
1 row in set (0.00 sec)
# 交互式超时时间,如数据库工具
mysql> show global variables like 'interactive_timeout';
+---------------------+-------+
| Variable_name | Value |
+---------------------+-------+
| interactive_timeout | 28800 |
+---------------------+-------+
1 row in set (0.00 sec)
MySQL 默认的最大连接数是 151 个(5.7 版本),最大是 16384(2^14)
mysql> show variables like 'max_connections';
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| max_connections | 128 |
+-----------------+-------+
1 row in set (0.00 sec)
查看 3306 端口当前连接数
[root@iZw ~]# netstat -an|grep 3306|wc -l
2
查看查询的执行状态。
SHOW FULL PROCESSLIST;
2、查询缓存(Query Cache)
MySQL 内部自带了一个缓存模块。默认是关闭的。主要是因为 MySQL 自带的缓存的应用场景有限,第一个是它要求 SQL 语句必须一模一样。第二个是表里面任何一条数据发生变化的时候,这张表所
有缓存都会失效。
当我们开启Mysql的查询缓存,当执行完全相同的SQL语句的时候,服务器就会直接从缓存中读取结果。当数据被修改, 之前的缓存会失效,所以修改比较频繁的表不适合做查询缓存。
查看当前mysql是否开启了查询缓存
SHOW VARIABLES LIKE 'query_cache_type';
查看查询缓存的占用大小
SHOW VARIABLES LIKE 'query_cache_size';
查看查询缓存的状态变量
SHOW STATUS LIKE 'Qcache%'
在 MySQL 5.8 中,查询缓存已经被移除了?
3.语法解析和预处理(Parser & Preprocessor)
服务器是怎么知道我输入的内容是错误的?或者,当我输入了一个语法完全正确的 SQL,但是表名不存在,它是怎么发现的?这个就是 MySQL 的 Parser 解析器和 Preprocessor 预处理模块。这一步主要做的事情是对 SQL 语句进行词法和语法分析和语义的解析。
词法解析
词法分析就是把一个完整的 SQL 语句打碎成一个个的单词。
比如一个简单的 SQL 语句:
select name from user where id = 1;
它会打碎成 8 个符号,记录每个符号是什么类型,从哪里开始到哪里结束。
语法解析
第二步就是语法分析,语法分析会对 SQL 做一些语法检查,比如单引号有没有闭合,然后根据 MySQL
定义的语法规则,根据 SQL 语句生成一个数据结构。这个数据结构我们把它叫做解析树。
预处理器(Preprocessor)
如果表名错误,会在预处理器处理时报错。
它会检查生成的解析树,解决解析器无法解析的语义。比如,它会检查表和列名是否存在,检查名
字和别名,保证没有歧义。
4、查询优化(Query Optimizer)与查询执行计划
问题:一条 SQL 语句是不是只有一种执行方式?或者说数据库最终执行的 SQL 是不是就是我们发送的 SQL?
这个答案是否定的。
一条 SQL 语句是可以有很多种执行方式的。但是如果有这么多种执行方式,这些执行方式怎么得到的?最终选择哪一种去执行?根据什么判断标准去选择?
这个就是 MySQL 的查询优化器的模块(Optimizer)。
查询优化器的目的就是根据解析树生成不同的执行计划,然后选择一种最优的执行计划,MySQL 里面使用的是基于开销(cost)的优化器,那种执行计划开销最小,就用哪种。
[MySQL]中可以通过show status like 'last_query_cost' 来查看查上一个查询的代价,而且它是io_cost和cpu_cost的开销总和,它通常也是我们评价一个查询的执行效率的一个常用指标。
SELECT * from `xllm_order` limit 100;
show status like 'Last_query_cost';
--代表需要随机读取几个 4K 的数据页才能完成查找。
优化器是怎么得到执行计划的?
首先我们要启用优化器的追踪(默认是关闭的)
mysql> SHOW VARIABLES LIKE 'optimizer_trace';
+-----------------+--------------------------+
| Variable_name | Value |
+-----------------+--------------------------+
| optimizer_trace | enabled=off,one_line=off |
+-----------------+--------------------------+
1 row in set (0.00 sec)
mysql> set optimizer_trace="enabled=on";
Query OK, 0 rows affected (0.00 sec)
mysql> SHOW VARIABLES LIKE 'optimizer_trace';
+-----------------+-------------------------+
| Variable_name | Value |
+-----------------+-------------------------+
| optimizer_trace | enabled=on,one_line=off |
+-----------------+-------------------------+
1 row in set (0.00 sec)
mysql>
注意开启这开关是会消耗性能的,因为它要把优化分析的结果写到表里面,所以不要轻易开启,或者查看完之后关闭它(改成 off)。
接着我们执行一个 SQL 语句,优化器会生成执行计划:
这个时候优化器分析的过程已经记录到系统表里面了,我们可以查询:
mysql> select * from information_schema.optimizer_trace\G
*************************** 1. row ***************************
QUERY: SELECT * FROM `crawler_list`
TRACE: {
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `crawler_list`.`id` AS `id`,`crawler_list`.`user_id` AS `user_id`,`crawler_list`.`title` AS `title`,`crawler_list`.`moment_type` AS `moment_type`,`crawler_list`.`status` AS `status` from `crawler_list`"
}
]
}
expanded_query 是优化后的 SQL 语句。
considered_execution_plans 里面列出了所有的执行计划。
关闭优化器的跟踪
mysql> set optimizer_trace="enabled=off";
Query OK, 0 rows affected (0.00 sec)
mysql> SHOW VARIABLES LIKE 'optimizer_trace';
+-----------------+--------------------------+
| Variable_name | Value |
+-----------------+--------------------------+
| optimizer_trace | enabled=off,one_line=off |
+-----------------+--------------------------+
1 row in set (0.00 sec)
优化器可以做什么?
MySQL 的优化器能处理哪些优化类型呢?
比如:
1、当我们对多张表进行关联查询的时候,以哪个表的数据作为基准表。
2、select * from user where a=1 and b=2 and c=3,如果 c=3 的结果有 100 条,b=2 的结果有200 条, a=1 的结果有 300 条,你觉得会先执行哪个过滤?
3、如果条件里面存在一些恒等或者恒不等的等式,是不是可以移除。
4、查询数据,是不是能直接从索引里面取到值。
5、count()、min()、max(),比如是不是能从索引里面直接取到值。
6、其他。
优化器得到的结果
优化器最终会把解析树变成一个查询执行计划,查询执行计划是一个数据结构。
当然,这个执行计划是不是一定是最优的执行计划呢?不一定,因为 MySQL 也有可能覆盖不到所
有的执行计划。
MySQL 提供了一个执行计划的工具。我们在 SQL 语句前面加上 EXPLAIN,就可以看到执行计划的信息。
5、存储引擎(Storage Engine)
我们的数据是放在哪里的?执行计划在哪里执行?是谁去执行?
存储引擎基本介绍
在关系型数据库里面,数据是放在表里面的。我们可以把这个表理解成 Excel 电子表格的形式。所以我们的表在存储数据的同时,还要组织数据的存储结构,这个存储结构就是由我们的存储引擎决定
的,所以我们也可以把存储引擎叫做表类型。
在 MySQL 里面,支持多种存储引擎,他们是可以替换的,所以叫做插件式的存储引擎。为什么要搞这么多存储引擎呢?一种还不够用吗?是因为我们在不同的业务场景中对数据操作的要求不同,这些不同的存储引擎通过提供不同的存储机制、索引方式、锁定水平等功能,来满足我们的业务需求。
查看数据库所有表
show table status from `mysql`;
在 MySQL 里面,我们创建的每一张表都可以指定它的存储引擎,它不是一个数据库只能使用一
个存储引擎。而且,创建表之后还可以修改存储引擎。
数据库存放数据的路径:
mysql> show variables like 'datadir';
+---------------+-------------------------+
| Variable_name | Value |
+---------------+-------------------------+
| datadir | /usr/local/mysql8/data/ |
+---------------+-------------------------+
1 row in set (0.00 sec)
每个数据库有一个自己文件夹,以 trainning 数据库为例。
任何一个存储引擎都有一个 frm 文件,这个是表结构定义文件。
我们在数据库中建了三张表,使用了不同的存储引擎。
不同的存储引擎存放数据的方式不一样,产生的文件也不一样。
6、执行引擎(Query Execution Engine),返回结果
执行引擎,它利用存储引擎提供了相应的 API 来完成对存储引擎的操作。最后把数据返回给客户端,即
使没有结果也要返回。
二、MySQL 体系结构总结
架构分层
总体上,我们可以把 MySQL 分成三层。
连接层(与客户端对接)
服务层(执行操作)
存储引擎层(跟硬件打交道
1.Connector:用来支持各种语言和 SQL 的交互,比如 PHP,Python,Java 的 JDBC
2.Management Serveices & Utilities:系统管理和控制工具,包括备份恢复、MySQL 复制、集群等等
3.Connection Pool:连接池,管理需要缓冲的资源,包括用户密码权限线程等等
4.SQL Interface:用来接收用户的 SQL 命令,返回用户需要的查询结果
5.Parser:用来解析 SQL 语句
6.Optimizer:查询优化器
7.Cache and Buffer:查询缓存,除了行记录的缓存之外,还有表缓存,Key 缓存,权限缓存等等。
8.Pluggable Storage Engines:插件式存储引擎,它提供 API 给服务层使用,跟具体的文件打交道。
三、一条更新SQL 是如何执行的?
在数据库里面,我们说的 update 操作其实包括了更新、插入和删除。更新流程和查询流程有什么不同呢?
基本流程也是一致的,也就是说,它也要经过解析器、优化器的处理,最后交给执行器。
区别就在于拿到符合条件的数据之后的操作。
首先,在 InnoDB 里面有个内存的缓冲池(buffer pool)。我们对数据的更新,不会每次都直接写到磁盘上,因为 IO 的代价太大了,所以先写入到 buffer pool 里面。内存的数据页和磁盘数据不一致的时候,我们把它叫做脏页。
InnoDB 里面有专门的把 buffer pool 的数据写入到磁盘的线程,每隔一段时间就一次性地把多个修改写入磁盘,这个就叫做刷脏。
这里面就有一个问题,如果在脏页还没有写入磁盘的时候,服务器出问题了,内存里面的数据丢失了。或者是刷脏刷到一半,甚至会破坏数据文件。所以我们必须要有一个持久化的机制。
redo log
InnoDB 引入了一个日志文件,叫做 redo log(重做日志),我们把所有对内存数据的修改操作写
入日志文件,如果服务器出问题了,我们就从这个日志文件里面读取数据,恢复数据——用它来实现事
务的持久性。
redo log
有什么特点?
1.记录修改后的值,属于物理日志
2.redo log 的大小是固定的,前面的内容会被覆盖,所以不能用于数据回滚/数据恢复。
3.redo log 是 InnoDB 存储引擎实现的,并不是所有存储引擎都有。
binlog
MySQL Server 层也有一个日志文件,叫做 binlog,它可以被所有的存储引擎使用。
binlog 以事件的形式记录了所有的 DDL 和 DML 语句(因为它记录的是操作而不是数据值,属于逻
辑日志),可以用来做主从复制和数据恢复。
跟 redo log 不一样,它的文件内容是可以追加的,没有固定大小限制。
例如一条语句:update teacher set name='jim' where name =‘666’
1、先查询到这条数据,如果有缓存,也会用到缓存。
2、把 name 改成jim,然后调用引擎的 API 接口,写入这一行数据到内存,同时记录 redo log。这时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,可以随时提交。
3、执行器收到通知后记录 binlog,然后调用存储引擎接口,设置 redo log 为 commit 状态。
4、更新完成。
问题:为什么要用两阶段提交(XA)呢?
举例:
如果我们执行的是把 name 改成jim,如果写完 redo log,还没有写 bin log 的时候,MySQL 重启了。
因为 redo log 可以恢复数据,所以写入磁盘的是jim。但是 bin log 里面没有记录这个逻辑日志,所以这时候用 binlog 去恢复数据或者同步到从库,就会出现数据不一致的情况。所以在写两个日志的情况下,binlog 就充当了一个事务的协调者。通知 InnoDB 来执行 prepare 或commit 或者 rollback。
简单地来说,这里有两个写日志的操作,类似于分布式事务,不用两阶段提交,就不能保证都成功或者都失败。