MySQL高级篇_第09章_性能分析工具的使用

2023-10-30

1. 数据库服务器的优化步骤

当我们遇到数据库调优问题的时候,该如何思考呢?这里把思考的流程整理成下面这张图。
整个流程划分成了 观察(Show status) 和 行动(Action) 两个部分。字母 S 的部分代表观察(会使用相应的分析工具),字母 A 代表的部分是行动(对应分析可以采取的行动)。

image-20220601185127151image-20220601185223736

小结:
image-20220601185244131

2. 查看系统性能参数

在MySQL中,可以使用 SHOW STATUS 语句查询一些MySQL数据库服务器的 性能参数执行频率
SHOW STATUS语句语法如下:

SHOW [GLOBAL|SESSION] STATUS LIKE '参数';

一些常用的性能参数如下:

  • Connections:连接MySQL服务器的次数。
  • Uptime:MySQL服务器的上线时间。
  • Slow_queries:慢查询的次数。
  • Innodb_rows_read:Select查询返回的行数
  • Innodb_rows_inserted:执行INSERT操作插入的行数
  • Innodb_rows_updated:执行UPDATE操作更新的行数
  • Innodb_rows_deleted:执行DELETE操作删除的行数
  • Com_select:查询操作的次数。
  • Com_insert:插入操作的次数。对于批量插入的 INSERT 操作,只累加一次。
  • Com_update:更新操作的次数。
  • Com_delete:删除操作的次数。

若查询MySQL服务器的连接次数,则可以执行如下语句:

SHOW STATUS LIKE "connections' ;

若查询服务器工作时间,则可以执行如下语句:

SHOW STATUS LIKE "Uptime ' ;

若查询MySQL服务器的慢查询次数,则可以执行如下语句:

SHOW STATUS LIKE 'Slow_queries ' ;

慢查询次数参数可以结合慢查询日志找出慢查询语句,然后针对慢查询语句进行表结构优化或者查询语句优化。再比如,如下的指令可以查看相关的指令情况:

show status like 'Innodb_rows_%';

3. 统计SQL的查询成本:last_query_cost

一条SQL查询语句在执行前需要确定查询执行计划,如果存在多种执行计划的话,MySQL会计算每个执行计划所需要的成本,从中选择 成本最小 的一个作为最终执行的执行计划。
如果我们想要查看某条SQL语句的查询成本,可以在执行完这条SQL语句之后,通过查看当前会话中的 last_query_cost 变量值来得到当前查询的成本。它通常也是我们 评价一个查询的执行效率的一个常用指标 。这个查询成本对应的是 SQL语句所需要读取的页的数量 。
我们依 然使用第8章的 student_info 表为例:

use atguigudb1;
 
CREATE TABLE `student_info` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`student_id` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`course_id` INT NOT NULL ,
`class_id` INT(11) DEFAULT NULL,
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

如果我们想要查询 id=900001 的记录,然后看下查询成本,我们可以直接在聚簇索引上进行查找:

mysql> select * from student_info where id = 900001;
 
+--------+------------+--------+-----------+----------+---------------------+
| id     | student_id | name   | course_id | class_id | create_time         |
+--------+------------+--------+-----------+----------+---------------------+
| 900001 |      40881 | LXFjgc |     10100 |    10087 | 2021-12-20 16:05:22 |
+--------+------------+--------+-----------+----------+---------------------+
1 row in set (0.03 sec)

运行结果(1 条记录,运行时间为 0.03s )
然后再看下查询优化器的成本,实际上我们只需要检索一个页即可:

mysql> SHOW STATUS LIKE 'last_query_cost';
+-----------------+----------+
| Variable_name  | Value  |
+-----------------+----------+
| Last_query_cost | 1.000000 |
+-----------------+----------+

如果我们想要查询 id 在 900001 到 9000100 之间的学生记录呢?

SELECT student_id, class_id, NAME, create_time FROM student_info
WHERE id BETWEEN 900001 AND 900100;

运行结果(100 条记录,运行时间为 0.046s ):

然后再看下查询优化器的成本,这时我们大概需要进行 20 个页的查询。

mysql> SHOW STATUS LIKE 'last_query_cost';
+-----------------+-----------+
| Variable_name  | Value   |
+-----------------+-----------+
| Last_query_cost | 21.134453 |
+-----------------+-----------+

你能看到页的数量是刚才的 20 倍,但是查询的效率并没有明显的变化,实际上这两个 SQL 查询的时间基本上一样,就是因为采用了顺序读取的方式将页面一次性加载到缓冲池中,然后再进行查找。虽然 页数量(last_query_cost)增加了不少 ,但是通过缓冲池的机制,并 没有增加多少查询时间

使用场景:它对于比较开销是非常有用的,特别是我们有好几种查询方式可选的时候。

SQL 查询是一个动态的过程,从页加载的角度来看,我们可以得到以下两点结论:
1. 位置决定效率。如果页就在数据库缓冲池中,那么效率是最高的,否则还需要从内存或者磁盘中进行读取,当然针对单个页的读取来说,如果页存在于内存中,会比在磁盘中读取效率高很多。
2. 批量决定效率。如果我们从磁盘中对单一页进行随机读,那么效率是很低的(差不多10ms),而采用顺序读取的方式,批量对页进行读取,平均一页的读取效率就会提升很多,甚至要快于单个页面在内存中的随机读取。

所以说,遇到I/O并不用担心,方法找对了,效率还是很高的。我们首先要考虑数据存放的位置,如果是经常使用的数据就要尽量放到缓冲池中,其次我们可以充分利用磁盘的吞吐能力,一次性批量读取数据,这样单个页的读取效率也就得到了提升。

4. 定位执行慢的SQL:慢查询日志

MySQL的慢查询日志,用来记录在MySQL中响应时间超过阈值的语句,具体指运行时间超过long_query_time的值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上(不含10秒)的语句,认为是超出了我们的最大忍耐时间值。

它的主要作用是,帮助我们发现那些执行时间特别长的SQL查询语句,并且有针对性地进行优化,从而提高系统的整体效率。当我们的数据库服务器发生阻塞、运行变慢的时候,检查一下慢查询日志,找到那些慢查询sql,对解决问题很有帮助。比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合explain进行全面分析。

默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。

慢查询日志支持将日志记录写入文件。

4.1 开启慢查询日志参数

  1. 开启slow_query_log
    在使用前,我们需要先看下慢查询是否已经开启,使用下面这条命令即可:

    set global slow_query_log='ON';
    

    然后我们再来查看下慢查询日志是否开启,以及慢查询日志文件的位置:

    show variables like `%slow_query_log%`;
    

    image-20220602111334290
    你能看到这时慢查询分析已经开启,同时文件保存在 /var/lib/mysql/atguigu1-slow.log 文件

  2. 修改long_query_time阈值
    接下来我们来看下慢查询的时间阈值设置,使用如下命令:

    show variables like '%long_query_time%';
    

    image-20220601190607244
    这里如果我们想把时间缩短,比如设置为 1 秒,可以这样设置:

    #测试发现:设置global的方式对当前session的long_query_time失效。对新连接的客户端有效。所以可以一并 执行下述语句 
    mysql > set global long_query_time = 1; 
    mysql> show global variables like '%long_query_time%'; 
    mysql> set long_query_time=1; 
    mysql> show variables like '%long_query_time%';
    

    补充:配置文件中一并设置参数
    如下的方式相较于前面的命令行方式,可以看作是永久设置的方式。
    修改my .cnf文件,[mysqld]下增加或修改参数 long_query_time 、slow_query_log 和 slow_query_log_file 后,然后重启MySQL服务器。

    [mysqld]
    slow_query_log=ON   #开启慢查询日志的开关
    slow.query_log_file=/var/lib/mysq1/atguigu-slow.log  #慢查询日志的目录和文件名信息
    long.query_time=3  #设置慢查询的阈值为3秒,超出此设定值的SQL即被记录到慢查询日志
    log_output=FILE
    

    如果不指定存储路径,慢查询日志将默认存储到MySQL数据库的数据文件夹下。如果不指定文件名,默认文件名为hostname-slow.log.

4.2 查看慢查询数目

查询当前系统中有多少条慢查询记录

SHOW GLOBAL STATUS LIKE '%Slow_queries%';

4.3 案例演示

步骤1. 建表

CREATE TABLE `student` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`stuno` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`classId` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

步骤2:设置参数 log_bin_trust_function_creators

创建函数,假如报错:

This function has none of DETERMINISTIC......

命令开启:允许创建函数设置:

set global log_bin_trust_function_creators=1;   # 不加global只是当前窗口有效。

步骤3:创建函数

随机产生字符串:(同上一章)

DELIMITER //
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255) #该函数会返回一个字符串
BEGIN
	DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
	DECLARE return_str VARCHAR(255) DEFAULT '';
	DECLARE i INT DEFAULT 0;
	WHILE i < n DO
		SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
		SET i = i + 1;
	END WHILE;
	RETURN return_str;
END //
DELIMITER ;
 
#测试
SELECT rand_string(10);

产生随机数值:(同上一章)

DELIMITER //
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN 
	DECLARE i INT DEFAULT 0; 
	SET i = FLOOR(from_num +RAND()*(to_num - from_num+1));
	RETURN i; 
END //
DELIMITER ;
 
#测试:
SELECT rand_num(10,100);

步骤4:创建存储过程

DELIMITER //
CREATE PROCEDURE insert_stu1(START INT , max_num INT)
BEGIN 
	DECLARE i INT DEFAULT 0; 
	SET autocommit = 0; #设置手动提交事务
	REPEAT	#循环
	SET i = i + 1;	#赋值
		INSERT INTO student (stuno, NAME ,age ,classId ) VALUES ((START+i),rand_string(6),rand_num(10,100),rand_num(10,1000)); 
	UNTIL i = max_num 
	END REPEAT; 
	COMMIT;	#提交事务
END //
DELIMITER ;

步骤5:调用存储过程

#调用刚刚写好的函数, 4000000条记录,从100001号开始
CALL insert_stu1(100001,4000000);

4.4 测试及分析

  1. 测试

    mysql> SELECT * FROM student WHERE stuno = 3455655;
    +---------+---------+--------+------+---------+
    | id   | stuno  | name  | age | classId |
    +---------+---------+--------+------+---------+
    | 3523633 | 3455655 | oQmLUr |  19 |    39 |
    +---------+---------+--------+------+---------+
    1 row in set (2.09 sec)
    
    
    mysql> SELECT * FROM student WHERE name = 'oQmLUr';
    +---------+---------+--------+------+---------+
    | id   | stuno  | name  | age | classId |
    +---------+---------+--------+------+---------+
    | 1154002 | 1243200 | OQMlUR |  266 |    28 |
    | 1405708 | 1437740 | OQMlUR |  245 |   439 |
    | 1748070 | 1680092 | OQMlUR |  240 |   414 |
    | 2119892 | 2051914 | oQmLUr |  17 |    32 |
    | 2893154 | 2825176 | OQMlUR |  245 |   435 |
    | 3523633 | 3455655 | oQmLUr |  19 |    39 |
    +---------+---------+--------+------+---------+
    6 rows in set (2.39 sec)
    

    从上面的结果可以看出来,查询学生编号为“3455655”的学生信息花费时间为2.09秒。查询学生姓名为“oQmLUr”的学生信息花费时间为2.39秒。已经达到了秒的数量级,说明目前查询效率是比较低的,下面的小节我们分析一下原因。

  2. 分析

    mysql> show status like 'slow_queries';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | Slow_queries  | 2     |
    +---------------+-------+
    1 row in set (0.00 sec)
    

    补充说明:
    除了上述变量,控制慢查询日志的还有一个系统变量: min_examined_row_limit。这个变量的意思是,查询 扫描过的最少记录数 。这个变量和查询执行时间,共同组成了判别一个查询是否是慢查询的条件。如果查询扫描过的记录数大于等于这个变量的值,并且查询执行时间超过long_query_time的值,那么,这个查询就被记录到慢查询日志中;反之,则不被记录到慢查询日志中。
    image-20220602113113131
    这个值默认是0。与long_query ]ime=10合在一起,表示只要查询的执行时间超过10秒钟,哪怕一个记录也没有扫描过,都要被记录到慢查询日志中。你也可以根据需要,通过修改"my.ini""文件,来修改查询时长,或者通过SET指令,用SQL语句修改"min_examined_row_limit"的值。

4.5 慢查询日志分析工具:mysqldumpslow

在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow
查看mysqldumpslow的帮助信息

mysqldumpslow --help

image-20220602150001264

mysqldumpslow 命令的具体参数如下:

  • -a: 不将数字抽象成N,字符串抽象成S
  • -s: 是表示按照何种方式排序:
    • c: 访问次数
    • l: 锁定时间
    • r: 返回记录
    • t: 查询时间
    • al:平均锁定时间
    • ar:平均返回记录数
    • at:平均查询时间 (默认方式)
    • ac:平均查询次数
  • -t: 即为返回前面多少条的数据;
  • -g: 后边搭配一个正则匹配模式,大小写不敏感的;

举例:我们想要按照查询时间排序,查看前五条 SQL 语句,这样写即可:

mysqldumpslow -s t -t 5 /var/lib/mysql/atguigu1-slow.log
[root@bogon ~]# mysqldumpslow -s t -t 5 /var/lib/mysql/atguigu1-slow.log
Reading mysql slow query log from /var/lib/mysql/atguigu01-slow.log
Count: 1  Time=2.39s (2s)  Lock=0.00s (0s) Rows=13.0 (13), root[root]@localhost
 SELECT * FROM student WHERE name = 'S'
Count: 1  Time=2.09s (2s)  Lock=0.00s (0s) Rows=2.0 (2), root[root]@localhost
 SELECT * FROM student WHERE stuno = N
Died at /usr/bin/mysqldumpslow line 162, <> chunk 2.

工作常用参考:

#得到返回记录集最多的10个SQL 
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu1-slow.log 
#得到访问次数最多的10个SQL 
mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu1-slow.log
#得到按照时间排序的前10条里面含有左连接的查询语句 
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu1-slow.log 
#另外建议在使用这些命令时结合 | 和more 使用 ,否则有可能出现爆屏情况 
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log | more

4.6 关闭慢查询日志

MySQL服务器停止慢查询日志功能有两种方法:

方式1:永久性方式

[mysqld] 
slow_query_log=OFF

或者,把slow_query_log一项注释掉 或 删除

[mysqld]
#slow_query_log =OFF

重启MySQL服务,执行如下语句查询慢日志功能。

SHOW VARIABLES LIKE '%slow%';  #查询慢查询日志所在目录
SHOW VARIABLES LIKE '%long_query_time%';  #查询超时时长

方式2:临时性方式

使用SET语句来设置。

(1)停止MySQL慢查询日志功能,具体SQL语句如下。

SET GLOBAL slow_query_log=off;

(2)重启MySQL服务,使用SHOW语句查询慢查询日志功能信息,具体SQL语句如下

SHOW VARIABLES LIKE '%slow%';
#以及
SHOW VARIABLES LIKE '%long_query_time%';

4.7 删除慢查询日志

使用SHOW语句显示慢查询日志信息,具体SQL语句如下。

SHOW VARIABLES LIKE 'slow_query_log%';

image-20220602114005482

从执行结果可以看出,慢查询日志的目录默认为MySQL的数据目录,在该目录下 手动删除慢查询日志文件 即可。使用命令 mysqladmin flush-logs 来重新生成查询日志文件,具体命令如下,执行完毕会在数据目录下重新生成慢查询日志文件。

mysqladmin -root -p flush-logs slow

提示:
慢查询日志都是使用mysqladmin flush-logs命令来删除重建的。使用时一定要注意,一旦执行了这个命令,慢查询日志都只存在新的日志文件中,如果需要旧的查询日志,就必须事先备份。

5. 查看 SQL 执行成本:SHOW PROFILE

show profile在《逻辑架构》章节中讲过,这里作为复习。

Show Profile是MySQL提供的可以用来分析当前会话中SQL都做了什么、执行语句资源消耗情况的工具,可用于sql调优的测量。默认情况下处于关闭状态,并保存最近15次的运行结果。

我们可以在会话级别开启这个功能

mysql > show variables like 'profiling';

image-20220601192623348
通过设置 profiling='ON’ 来开启 show profile:

mysql > set profiling = 'ON';

image-20220601192654673
然后执行相关的查询语句。接着看下当前会话都有哪些 profiles,使用下面这条命令:

mysql > show profiles;

image-20220601192747332
你能看到当前会话一共有 2 个查询。如果我们想要查看最近一次查询的开销,可以使用:

mysql > show profile;

image-20220601192824378

我们也可以查看指定的Query ID的开销,比如 show profile for query 2 查询结果是一样的。在SHOW PROFILE 中我们可以查看不同部分的开销,比如cpu、block.io等:

mysql> show profile cpu,block io for query 2;

image-20220601192847113

show profile的常用查询参数:
① ALL:显示所有的开销信息。
② BLOCK IO:显示块IO开销。
③ CONTEXT SWITCHES:上下文切换开销。
④ CPU:显示CPU开销信息。
⑤ IPC:显示发送和接收开销信息。
⑥ MEMORY:显示内存开销信息。
⑦ PAGE FAULTS:显示页面错误开销信息。
⑧ SOURCE:显示和Source_function,Source_file,Source_line相关的开销信息。
⑨ SWAPS:显示交换次数开销信息。

日常开发需注意的结论:
①converting HEAP to MyISAM:查询结果太大,内存不够,数据往磁盘上搬了。
②Creating tmp table:创建临时表。先拷贝数据到临时表,用完后再删除临时表。
③Copying to tmp table on disk:把内存中临时表复制到磁盘上,警惕!
④locked。

如果在show profile诊断结果中出现了以上4条结果中的任何一条,则sql语句需要优化。

注意:
不过SHOW PROFILE命令将被弃用,我们可以从information_schema中的profiling数据表进行查看。

6. 分析查询语句:EXPLAIN

6.1 概述

定位了查询慢的SQL之后,我们就可以使用EXPLAIN或 DESCRIBE 工具做针对性的分析查询语句。DESCRIBE语句的使用方法与EXPLAIN语句是一样的,并且分析结果也是一样的。
MySQL中有专门负责优化 SELECT 语句的优化器模块,主要功能:通过计算并分析系统中收集到的统计信息,为客户端请求的Query提供它认为最优的 执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)。
这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来执行查询等等。MySQL为我们提供了 EXPLAIN 语句来帮助我们查看某个查询语句的具体执行计划,大家看懂EXPLAIN语句的各个输出项,可以有针对性的提升我们查询语句的性能。
1.能做什么?

  • 表的读取顺序
  • 数据读取操作的操作类型
  • 哪些索引可以使用
  • 哪些索引被实际使用
  • 表之间的引用
  • 每张表有多少行被优化器查

2.官网介绍

https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
https://dev.mysql.com/doc/refman/8.0/en/explain-output.html

image-20220602093621108
版本情况

  • MySQL 5.6.3以前只能 EXPLAIN SELECT ;MYSQL 5.6.3以后就可以 EXPLAIN SELECT,UPDATE,DELETE
  • 在5.7以前的版本中,想要显示 partitions 需要使用 explain partitions 命令;想要显示 filtered 需要使用 explain extended 命令。在5.7版本后,默认explain直接显示partitions和filtered中的信息。
    image-20220602121943520

6.2 基本语法

EXPLAIN 或 DESCRIBE语句的语法形式如下:

EXPLAIN SELECT select_options 
#或者
DESCRIBE SELECT select_options

如果我们想看看某个查询的执行计划的话,可以在具体的查询语句前边加一个 EXPLAIN ,就像这样:

mysql> EXPLAIN SELECT 1;

image-20220602122206036

输出的上述信息就是所谓的 执行计划 。在这个执行计划的辅助下,我们需要知道应该怎样改进自己的查询语句以使得执行起来更高效。其实除了以 SELECT 开头的查询语句,其余的 DELETE、INSERT、REPLACE 以及 UPDATE 语句等都可以加上EXPLAIN,用来查看这些语句的执行计划,只是平时我们对SELECT语句更感兴趣。

注意:执行EXPLAIN时并没有真正的执行该后面的语句,因此可以安全的查看执行计划。

EXPLAIN 语句输出的各个列的作用如下:

列名 描述
id 在一个大的查询语句中每个SELECT关键字都对应一个唯一的id
select_type SELECT关键字对应的那个查询的类型
table 表名
partitions 匹配的分区信息
type 针对单表的访问方法
possible_keys 可能用到的索引
key 实际上使用的索引
key_len 实际使用到的索引长度
ref 当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows 预估的需要读取的记录条数
filtered 某个表经过搜索条件过滤后剩余记录条数的百分比
Extra 一些额外的信息

在这里把它们都列出来只是为了描述一个轮廓,让大家有一个大致的印象。

6.3 数据准备

  1. 建表
    use atguigudb1;
    
    CREATE TABLE s1 (
    	id INT,
    	key1 VARCHAR (100),
    	key2 INT,
    	key3 VARCHAR (100),
    	key_part1 VARCHAR (100),
    	key_part2 VARCHAR (100),
    	key_part3 VARCHAR (100),
    	common_field VARCHAR (100),
    	PRIMARY KEY (id),
    	INDEX idx_key1 (key1),
    	UNIQUE INDEX idx_key2 (key2),
    	INDEX idx_key3 (key3),
    	INDEX idx_key_part (key_part1, key_part2, key_part3) 
    ) ENGINE = INNODB CHARSET = utf8;
    
    
    CREATE TABLE s2 (
    	id INT AUTO_INCREMENT,
    	key1 VARCHAR ( 100 ),
    	key2 INT,
    	key3 VARCHAR ( 100 ),
    	key_part1 VARCHAR ( 100 ),
    	key_part2 VARCHAR ( 100 ),
    	key_part3 VARCHAR ( 100 ),
    	common_field VARCHAR ( 100 ),
    	PRIMARY KEY ( id ),
    	INDEX idx_key1 ( key1 ),
    	UNIQUE INDEX idx_key2 ( key2 ),
    	INDEX idx_key3 ( key3 ),
    	INDEX idx_key_part ( key_part1, key_part2, key_part3 ) 
    ) ENGINE = INNODB CHARSET = utf8;
    
  2. 设置参数 log_bin_trust_function_creators
    创建函数,假如报错,需开启如下命令:允许创建函数设置:
    set global log_bin_trust_function_creators=1;   # 不加global只是当前窗口有效
    
  3. 创建函数
    DELIMITER //
    CREATE FUNCTION rand_string1(n INT)
    RETURNS VARCHAR(255) #该函数会返回一个字符串
    BEGIN
    	DECLARE chars_str VARCHAR(100) DEFAULT
    'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
    	DECLARE return_str VARCHAR(255) DEFAULT '';
    	DECLARE i INT DEFAULT 0;
    	WHILE i < n DO
    		SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
    		SET i = i + 1;
    	END WHILE;
    	RETURN return_str;
    END //
    DELIMITER ;
    
  4. 创建存储过程
    创建往s1表中插入数据的存储过程:
    DELIMITER $
    CREATE PROCEDURE insert_s1(IN min_num INT (10),IN max_num INT (10))
    BEGIN
    DECLARE i INT DEFAULT 0;
    SET autocommit = 0;
    REPEAT
    SET i = i + 1;
    INSERT INTO s1 VALUES(
    	(min_num + i),
    	rand_string1(6),
    	(min_num + 30 * i + 5),
    	rand_string1(6),
    	rand_string1(10),
    	rand_string1(5),
    	rand_string1(10),
    	rand_string1(10));
    UNTIL i = max_num
    END REPEAT;
    COMMIT;
    END $
    DELIMITER ;
    
    创建往s2表中插入数据的存储过程:
    DELIMITER //
    CREATE PROCEDURE insert_s2 (IN min_num INT ( 10 ),IN max_num INT ( 10 )) 
    BEGIN
    	DECLARE i INT DEFAULT 0;
    	SET autocommit = 0;
    	REPEAT
    		SET i = i + 1;
    		INSERT INTO s2 VALUES(
    		( min_num + i ),
    		rand_string1 ( 6 ),
    		( min_num + 30 * i + 5 ),
    		rand_string1 ( 6 ),
    		rand_string1 ( 10 ),
    		rand_string1 ( 5 ),
    		rand_string1 ( 10 ),
    		rand_string1 ( 10 )
    		);
    		UNTIL i = max_num 
    	END REPEAT;
    	COMMIT;
    
    END // 
    DELIMITER;
    
    调用存储过程
    s1表数据的添加:加入1万条记录:
    CALL insert_s1(10001,10000);
    
    s2表数据的添加:加入1万条记录:
    CALL insert_s2(10001,10000);
    

6.4 EXPLAIN各列作用

为了让大家有比较好的体验,我们调整了下 EXPLAIN 输出列的顺序。

  1. table
    不论我们的查询语句有多复杂,包含了多少个表 ,到最后也是需要对每个表进行单表访问的,所以MySQL规定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该表的表名(有时不是真实的表名字,可能是简称)。

  2. id
    我们写的查询语句一般都以 SELECT 关键字开头,比较简单的查询语句里只有一个 SELECT 关键字,比如下边这个查询语句:

    SELECT * FROM s1 WHERE key1 = 'a';
    

    稍微复杂一点的连接查询中也只有一个 SELECT 关键字,比如:

    SELECT * FROM s1 INNER JOIN s2
    ON s1.key1 = s2.key1
    WHERE s1.common_field = 'a';
    
    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
    

    image-20220602103358511

    mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2;
    

    image-20220602103624684

    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
    

    image-20220602103714558

    #查询优化器可能对涉及子查询的查询语句进行重写,转变为多表查询的操作
    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key2 FROM s2 WHERE common_field = 'a');
    

    image-20220602103803299

    EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
    

    image-20220602103838045

    EXPLAIN SELECT * FROM s1  UNION ALL SELECT * FROM s2;
    

    image-20220602103917310

小结:

  • id如果相同,可以认为是一组,从上往下顺序执行
  • 在所有组中,id值越大,优先级越高,越先执行
  • 关注点:id号每个号码,表示一趟独立的查询,一个sql的查询趟数越少越好
  1. select_type
    一条大的查询语句里边可以包含若干个SELECT关键字,每个SELECT关键字代表着一个小的查询语句,而每个SELECT关键字的FROM子句中都可以包含若干张表(这些表用来做连接查询),每一张表都对应着执行计划输出中的一条记录,对于在同一个SELECT 关键字中的表来说,它们的id值是相同的。
    MySQL为每一个SELECT关键字代表的小查询都定义了一个称之为 select_type 的属性,意思是我们只要知道了某个小查询的 select_type属性,就知道了这个 小查询在整个大查询中扮演了一个什么角色 ,我们看一下select_type都能取哪些值,请看官方文档:

    image-20220601205011273

    具体分析如下:

    • SIMPLE
      查询语句中不包含UNION或者子查询的查询都算作是SIMPLE类型
    mysql> EXPLAIN SELECT * FROM s1;
    

    image-20220602103956528
    当然,连接查询也算是 SIMPLE 类型,比如:

    mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2;
    

    image-20220602104030877

    • PRIMARY
    mysql> EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
    

    image-20220602155609722

    • UNION
    • UNION RESULT
    • SUBQUERY
    mysql>  EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
    

    image-20220602155725704

    • DEPENDENT SUBQUERY
    EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE s1.key2 =s2.key2) OR key3 = 'a';
    

    image-20220602160034690

    • DEPENDENT UNION
    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 ='a' UNION SELECT key1 FROM s1 WHERE key1 = 'b');
    

    image-20220602160225130

    • DERIVED
    mysql> EXPLAIN SELECT * FROM (SELECT key1, count(*) as c FROM s1 GROUP BY key1) AS
    derived_s1 where c > 1;
    
    • MATERIALIZED
    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2);
    

    image-20220602160602273

    • UNCACHEABLE SUBQUERY
      不常用,略
    • UNCACHEABLE UNION
      不常用,略
  2. partitions(了解)
    代表分区表中的命中情况,非分区表,该项为 NULL。一般情况下我们的查询语句的执行计划的 partitions 列的值都是 NULL。
    https://dev.mysql.com/doc/refman/5.7/en/alter-table-partition-operations.html
    如果想详细了解,可以如下方式测试。创建分区表:

    -- 创建分区表,
    -- 按照id分区,id<100 p0分区,其他p1分区
    CREATE TABLE user_partitions (
    	id INT auto_increment,
    	NAME VARCHAR(12),PRIMARY KEY(id)
    	)
    PARTITION BY RANGE(id)(
    PARTITION p0 VALUES less than(100),
    PARTITION p1 VALUES less than MAXVALUE
    );
    
    mysql> DESC SELECT * FROM user_partitions WHERE id>200;
    

    查询id大于200(200>100,p1分区)的记录,查看执行计划,partitions是p1,符合我们的分区规则

    image-20220602161204331

  3. type(重点)
    执行计划的一条记录就代表着MySQL对某个表的 执行查询时的访问方法 ,又称“访问类型”,其中的 type 列就表明了这个访问方法是啥,是较为重要的一个指标。比如,看到type列的值是ref,表明MySQL即将使用ref访问方法来执行对s1表的查询。
    完整的访问方法如下: system , const , eq_ref , ref , fulltext , ref_or_null ,index_merge , unique_subquery , index_subquery , range , index , ALL 。
    我们详细解释一下:

    • system
      当表中 只有一条记录 并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问方法就是system。比方说我们新建一个 MyISAM 表,并为其插入一条记录:

      mysql> CREATE TABLE t(i int) Engine=MyISAM;
      Query OK, 0 rows affected (0.00 sec)
      mysql> INSERT INTO t VALUES(1);
      Query OK, 1 row affected (0.00 sec)
      

      然后我们看一下查询这个表的执行计划:

      image-20220602162054108

    • const

      mysql> EXPLAIN SELECT * FROM s1 WHERE id = 10005;
      

      image-20220602162236274

    • eq_ref

      mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
      

      image-20220602164813514
      从执行计划的结果中可以看出,MySQL打算将s2作为驱动表,s1作为被驱动表,重点关注s1的访问
      方法是 eq_ref ,表明在访问s1表的时候可以 通过主键的等值匹配 来进行访问。

    • ref

      mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
      

      image-20220602165009464

    • fulltext
      全文索引

    • ref_or_null

      mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key1 IS NULL;
      

      image-20220602165239187

    • index_merge

      mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
      

      image-20220602165330109
      从执行计划的 type 列的值是 index_merge 就可以看出,MySQL 打算使用索引合并的方式来执行对 s1 表的查询。

    • unique_subquery

      mysql> EXPLAIN SELECT * FROM s1 WHERE key2 IN (SELECT id FROM s2 where s1.key1 = s2.key1) OR key3 = 'a';
      

      image-20220602165613069

    • index_subquery

      mysql> EXPLAIN SELECT * FROM s1 WHERE common_field IN (SELECT key3 FROM s2 where s1.key1 = s2.key1) OR key3 = 'a';
      

      image-20220602165652199

    • range

      mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c');
      -- 或者
      mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'a' AND key1 < 'b';
      

      image-20220602165838607

    • index

      mysql> EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a';
      

      image-20220602170001261

    • ALL

      mysql> EXPLAIN SELECT * FROM s1;
      

      image-20220602170046252

小结:

结果值从最好到最坏依次是:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery >range > index > ALL,其中比较重要的几个提取出来(标红)

SQL性能优化的目标:至少要达到 range级别,要求是ref级别,最好是consts级别。(阿里巴巴开发手册要求)

  1. possible_keys和key
    在EXPLAIN语句输出的执行计划中, possible_keys 列表示在某个查询语句中,对某个表执行 单表查询时可能用到的索引 有哪些。一般查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。key 列表示 实际用到的索引 有哪些,如果为NULL,则没有使用索引。比方说下边这个查询:

    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';
    

    image-20220602170702967

  2. key_len(重点)

    mysql> EXPLAIN SELECT * FROM s1 WHERE id = 10005;
    

    image-20220602170835015

    mysql> EXPLAIN SELECT * FROM s1 WHERE key2 = 10126;
    

    image-20220602170911995

    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
    

    image-20220602170954719

    mysql> EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a';
    

    image-20220602171023122

    mysql> EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';
    

    image-20220602171056920

    练习:

    key_len的长度计算公式:

    varchar(10)变长字段且允许NULL = 10 * ( character set: utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)

    varchar(10)变长字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+2(变长字段)

    char(10)固定字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)

    char(10)固定字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)

  3. ref
    显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。

    当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是 const、eqmref、ref、ref_or_null.unique_subquery、index_subquery 其中之一时,ref列展示的就是与索引列作等值匹配的结构是什么,比如只是一个常数或者是某个列。大家看下边这个查询:

    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
    

    image-20220602171259920

    可以看到 ref 列的值是 const,表明在使用 idx_key1 索引执行查询时,与key1 列作等值匹配的对象是一个常数,当然有时候更复杂一点:

    mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
    

    image-20220602171331011

    mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.key1 = UPPER(s1.key1);
    

    image-20220602171350962

  4. rows(重点)
    预估的需要读取的记录条数

    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z';
    

    image-20220602171442706

  5. filtered
    某个表经过搜索条件过滤后剩余记录条数的百分比

    如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用到对应索引的搜索条件外的其他搜索条件的记录有多少条。

    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND common_field = 'a';
    

    image-20220602171554487
    对于单表查询来说,这个filtered列的值没什么意义,我们更关注在连接查询中驱动表对应的执行计划记录的filtered值,它决定了被驱动表要执行的次数(即:rows * filtered)

    mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';
    

    image-20220602171617891
    从执行计划中可以看出来,查询优化器打算把 s2 当作驱动表,s1 当作被驱动表。我们可以看到驱动表 s2 表的执行计划的 rows 列为9895,filtered列为100.00,这意味着驱动表s1的扇出值就是9688 x 100.0% =9895,这说明还要对被驱动表执行大约 9895 次查询。

  6. Extra(重要)
    顾名思义,Extra 列是用来说明一些额外信息的,包含不适合在其他列中显示但十分重要的额外信息。我们可以通过这些额外信息来 更准确的理解MySQL到底将如何执行给定的查询语句 。MySQL提供的额外信息有好几十个,我们就不一个一个介绍了,所以我们只挑比较重要的额外信息介绍给大家。

    • No tables used
      当查询语句的没有 FROM 子句时将会提示该额外信息,比如:

      mysql> explain select 1;
      

      在这里插入图片描述

    • Impossible WHERE
      查询语句的WHERE子句永远为FALSE时将会提示该额外信息

      mysql> EXPLAIN SELECT * FROM s1 WHERE 1 != 1;
      

      image-20220602172145221

    • Using where
      不用读取表中所有信息,仅通过索引就可以获取所需数据,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤。表明使用了where过滤。

      当我们使用全表扫描来执行对某个表的查询,并且该语句的WHERE子句中有针对该表的搜索条件时,在Extra列中会提示上述额外信息。

      mysql> EXPLAIN SELECT * FROM s1 WHERE common_field = 'a';
      

      image-20220602172225642

      当使用索引访问来执行对某个表的查询,并且该语句的WHERE子句中有除了该索引包含的列之外的其他搜索条件时,在Extra列中也会提示上述额外信息。比如下边这个查询虽然使用 idx_key1 索引执行查询,但是搜索条件中除了包含 key1 的搜索条件 key1 = ‘a’ ,还包含 common_field 的搜索条件,所以 Extra 列会显示 Using where 的提示:

      mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND common_field = 'a';
      

      image-20220602172306779

    • No matching min/max row
      当查询列表处有MIN或者MAX聚合函数,但是并没有符合WHERE子句中的搜索条件的记录时,将会提示该额外信息

      mysql> EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'abcdefg';
      

      image-20220602172341087

    • Using index
      当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用索引覆盖的情况下,在Extra列将会提示该额外信息。比方说下边这个查询中只需要用到idx_key1而不需要回表操作:

      mysql> EXPLAIN SELECT key1 FROM s1 WHERE key1 = 'a';
      

      image-20220602172439106

    • Using index condition
      有些搜索条件中虽然出现了索引列,但却不能使用到索引,比如下边这个查询:

      mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%b';
      

      其中的 key1 > ‘z’ 可以使用到索引,但是 key1 LIKE ‘%a’ 却无法使用到索引,在以前版本的MySQL中,是按照下边步骤来执行这个查询的:

      • 先根据 key1 > ‘z’ 这个条件,从二级索引 idx_key1 中获取到对应的二级索引记录。
      • 根据上一步骤得到的二级索引记录中的主键值进行 回表 ,找到完整的用户记录再检测该记录是否符合 key1 LIKE ‘%a’ 这个条件,将符合条件的记录加入到最后的结果集。
      • 但是虽然 key1 LIKE‘%a ’ 不能组成范围区间参与 range 访问方法的执行,但这个条件毕竟只涉及到了key1列,所以MySQL把上边的步骤改进了一下:
      • 先根据 key1 > ‘z’ 这个条件,定位到二级索引 idx_key1中对应的二级索引记录。
      • 对于指定的二级索引记录,先不着急回表,而是先检测一下该记录是否满足 key1 LIKE‘%a’ 这个条件,如果这个条件不满足,则该二级索引记录压根儿就没必要回表。
      • 对于满足 key1 LIKE ‘%a’ 这个条件的二级索引记录执行回表操作。

    我们说回表操作其实是一个 随机I0 ,比较耗时,所以上述修改虽然只改进了一点点,但是可以省去好多回表操作的成本。MySQL把他们的这个改进称之为 索引条件下推 (英文名: Index Condition Pushdown )。如果在查询语句的执行过程中将要使用索引条件下推这个特性,在Extra列中将会显示 Using index condition ,比如这样:

    mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%b';
    

    image-20220602172537452

    • Using join buffer (Block Nested Loop)
      在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫join buffer的内存块来加快查询速度,也就是我们所讲的基于块的嵌套循环算法
    mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field;
    

    image-20220602172625834
    可以在对s2表的执行计划的Extra列显示了两个提示:
    Using join buffer (Block Nested Loop)︰这是因为对表s2的访问不能有效利用索引,只好退而求其次,使用 join buffer 来减少对s2表的访问次数,从而提高性能。

    • Using where :可以看到查询语句中有一个 s1.common_field = s2.common_field 条件,因为s1是驱动表,s2是被驱动表,所以在访问s2表时, s1.common_field的值已经确定下来了,所以实际上查询s2表的条件就是s2.common_field = 一个常数,所以提示了Using where额外信息。

    • Not exists
      当我们使用左(外)连接时,如果 WHERE 子句中包含要求被驱动表的某个列等于 NULL 值的搜索条件,而且那个列又是不允许存储NULL值的,那么在该表的执行计划的Extra列就会提示 Not exists 额外信息,比如这样:

      mysql> EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;
      

      image-20220602172707071
      上述查询中 s1 表是驱动表,s2 表是被驱动表, s2.id 列是不允许存储NULL值的,而WHERE子句中又包含 s2.id IS NULL 的搜索条件,这意味着必定是驱动表的记录在被驱动表中找不到匹配ON子句条件的记录才会把该驱动表的记录加入到最终的结果集,所以对于某条驱动表中的记录来说,如果能在被驱动表中找到1条符合ON子句条件的记录,那么该驱动表的记录就不会被加入到最终的结果集,也就是说我们没有必要到被驱动表中找到全部符合ON子句条件的记录,这样可以稍微节省一点性能。

    • Using intersect(…) 、 Using union(…) 和 Using sort_union(…)
      如果执行计划的Extra列出现了 Using intersect(…) 提示,说明准备使用 Intersect 索引合并的方式执行查询,括号中的…表示需要进行索引合并的索引名称;如果出现了 Using union(. …) 提示,说明准备使用 Union 索引合并的方式执行查询;出现了 Using sort_union( . …) 提示,说明准备使用 Sort-Union 索引合并的方式执行查询。比如这个查询的执行计划:

      mysql> EXPLAIN SELECT * FROM s1 WHERE key1 ='a' OR key3 = 'a ' ;
      

      image-20220602172911183
      其中Extra列就显示了 Using union(idx_key3, idx_key1),表明MySQL即将使用idx_key3和idx_key1这两个索引进行Union索引合并的方式执行查询。

    • Zero limit
      当我们的 LIMIT 子句的参数为 0 时,表示压根儿不打算从表中读出任何记录,将会提示该额外信息,比如这样:

      mysql> EXPLAIN SELECT * FROM s1 LIMIT 0;
      

      image-20220602173003393

    • Using filesort
      有一些情况下对结果集中的记录进行排序是可以使用到索引的,比如下边这个查询:

      mysql> EXPLAIN SELECT * FROM s1 ORDER BY key1  LIMIT 10
      

      image-20220603131614695
      这个查询语句可以利用 idx_key1 索引直接取出key1列的10条记录,然后再进行回表操作就好了。但是很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序(英文名: filesort )。如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的Extra列中显示 Using filesort 提示,比如这样:

      mysql> EXPLAIN SELECT * FROM s1 ORDER BY common_field LIMIT 10
      

      image-20220602173059977

    • Using temporary
      在许多查询的执行过程中,MySQL可能会借助临时表来完成一些功能,比如去重、排序之类的,比如我们在执行许多包含DISTINCT、GROUP BY、UNION等子句的查询过程中,如果不能有效利用索引来完成查询,MySQL很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,在执行计划的Extra列将会显示Using temporary提示

      mysql> EXPLAIN SELECT DISTINCT common_field FROM s1;
      

      image-20220602173151518
      再比如:

      mysql> EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field;
      

      image-20220602173308679

      mysql> EXPLAIN SELECT key1, COUNT(*) AS amount FROM s1 GROUP BY key1;
      

      image-20220602173440437
      从 Extra 的 Using index 的提示里我们可以看出,上述查询只需要扫描 idx_key1 索引就可以搞定了,不再需要临时表了。

    • 其它

  7. 小结

  • EXPLAIN不考虑各种Cache
  • EXPLAIN不能显示MySQL在执行查询时所作的优化工作
  • EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况
  • 部分统计信息是估算的,并非精确值
  1. 练习
     #3. select_type:SELECT关键字对应的那个查询的类型,确定小查询在整个大查询中扮演了一个什么角色#########################
    
     # 查询语句中不包含`UNION`或者子查询的查询都算作是`SIMPLE`类型
     EXPLAIN SELECT * FROM s1;
    
    
     #连接查询也算是`SIMPLE`类型
     EXPLAIN SELECT * FROM s1 INNER JOIN s2;
    
    
    #对于包含`UNION`或者`UNION ALL`或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个
     #查询的`select_type`值就是`PRIMARY`
    
    
    #对于包含`UNION`或者`UNION ALL`的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询
     #以外,其余的小查询的`select_type`值就是`UNION`
    
    #`MySQL`选择使用临时表来完成`UNION`查询的去重工作,针对该临时表的查询的`select_type`就是
     #`UNION RESULT`
     EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
    
     EXPLAIN SELECT * FROM s1 UNION ALL SELECT * FROM s2;
    
    #子查询:
    #如果包含子查询的查询语句不能够转为对应的`semi-join`的形式,并且该子查询是不相关子查询。
     #该子查询的第一个`SELECT`关键字代表的那个查询的`select_type`就是`SUBQUERY`
     EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
    
    
     #如果包含子查询的查询语句不能够转为对应的`semi-join`的形式,并且该子查询是相关子查询,
     #则该子查询的第一个`SELECT`关键字代表的那个查询的`select_type`就是`DEPENDENT SUBQUERY`
     EXPLAIN SELECT * FROM s1 
     WHERE key1 IN (SELECT key1 FROM s2 WHERE s1.key2 = s2.key2) OR key3 = 'a';
    #注意的是,select_type为`DEPENDENT SUBQUERY`的查询可能会被执行多次。
    
    
     #在包含`UNION`或者`UNION ALL`的大查询中,如果各个小查询都依赖于外层查询的话,那除了
     #最左边的那个小查询之外,其余的小查询的`select_type`的值就是`DEPENDENT UNION`。
    EXPLAIN SELECT * FROM s1 
     WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 = 'a' UNION SELECT key1 FROM s1 WHERE key1 = 'b');
    
    
    #对于包含`派生表`的查询,该派生表对应的子查询的`select_type`就是`DERIVED`
     EXPLAIN SELECT * 
     FROM (SELECT key1, COUNT(*) AS c FROM s1 GROUP BY key1) AS derived_s1 WHERE c > 1;
    
    
     #当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,
     #该子查询对应的`select_type`属性就是`MATERIALIZED`
    EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2); #子查询被转为了物化表
    
    
    
    ##########################4. partition(略):匹配的分区信息#################################################
    
    
     # 5. type:针对单表的访问方法#################################################################
    
     #当表中`只有一条记录`并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,
     #那么对该表的访问方法就是`system`。
    CREATE TABLE t(i INT) ENGINE=MYISAM;
    INSERT INTO t VALUES(1);
    
     EXPLAIN SELECT * FROM t;
    
    #换成InnoDB
     CREATE TABLE tt(i INT) ENGINE=INNODB;
     INSERT INTO tt VALUES(1);
     EXPLAIN SELECT * FROM tt;
    
    
     #当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是`const`
     EXPLAIN SELECT * FROM s1 WHERE id = 10005;
    
     EXPLAIN SELECT * FROM s1 WHERE key2 = 10066;
    
    
     #在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的
     #(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则
     #对该被驱动表的访问方法就是`eq_ref`
     EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
    
    
    #当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是`ref`
     EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
    
    
     #当对普通二级索引进行等值匹配查询,该索引列的值也可以是`NULL`值时,那么对该表的访问方法
     #就可能是`ref_or_null`
     EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key1 IS NULL;
    
    
     #单表访问方法时在某些场景下可以使用`Intersection`、`Union`、
     #`Sort-Union`这三种索引合并的方式来执行查询
     EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
    
    
     #`unique_subquery`是针对在一些包含`IN`子查询的查询语句中,如果查询优化器决定将`IN`子查询
     #转换为`EXISTS`子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的`type`
     #列的值就是`unique_subquery`
     EXPLAIN SELECT * FROM s1 
     WHERE key2 IN (SELECT id FROM s2 WHERE s1.key1 = s2.key1) OR key3 = 'a';
    
    
     #如果使用索引获取某些`范围区间`的记录,那么就可能使用到`range`访问方法
     EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c');
    
    #同上
     EXPLAIN SELECT * FROM s1 WHERE key1 > 'a' AND key1 < 'b';
    
    
     #当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是`index`
     EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a';
    
    
     #最熟悉的全表扫描
     EXPLAIN SELECT * FROM s1;
    
    
     #6. possible_keys和key:可能用到的索引 和  实际上使用的索引####################################################
    
     EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';
    
    
    
    #7.  key_len:实际使用到的索引长度(即:字节数)####################################################################
    # 帮你检查`是否充分的利用上了索引`,`值越大越好`,主要针对于联合索引,有一定的参考意义。
     EXPLAIN SELECT * FROM s1 WHERE id = 10005;
    
    
     EXPLAIN SELECT * FROM s1 WHERE key2 = 10126;
    
    
     EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
    
    
     EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a';
    
    
     EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';
    
     EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c';
    
     EXPLAIN SELECT * FROM s1 WHERE key_part3 = 'a';
    
    #练习:
    #varchar(10)变长字段且允许NULL  = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)
    
    #varchar(10)变长字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+2(变长字段)
    
    #char(10)固定字段且允许NULL    = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)
    
    #char(10)固定字段且不允许NULL  = 10 * ( character set:utf8=3,gbk=2,latin1=1)
    
    
    
     # 8. ref:当使用索引列等值查询时,与索引列进行等值匹配的对象信息。##############
     #比如只是一个常数或者是某个列。
    
    EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
    
    
     EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
    
    
     EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.key1 = UPPER(s1.key1);
    
    
     # 9. rows:预估的需要读取的记录条数#################################################
     # `值越小越好`
     EXPLAIN SELECT * FROM s1 WHERE key1 > 'z';
    
    
    
     # 10. filtered: 某个表经过搜索条件过滤后剩余记录条数的百分比
    
    #如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用
     #到对应索引的搜索条件外的其他搜索条件的记录有多少条。
     EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND common_field = 'a';
    
    
     #对于单表查询来说,这个filtered列的值没什么意义,我们`更关注在连接查询
    #中驱动表对应的执行计划记录的filtered值`,它决定了被驱动表要执行的次数(即:rows * filtered)
     EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';
    
    
     #11. Extra:一些额外的信息############################################
     #更准确的理解MySQL到底将如何执行给定的查询语句
    
    
     #当查询语句的没有`FROM`子句时将会提示该额外信息
     EXPLAIN SELECT 1;
    
    
     #查询语句的`WHERE`子句永远为`FALSE`时将会提示该额外信息
    EXPLAIN SELECT * FROM s1 WHERE 1 != 1;
    
    
     #当我们使用全表扫描来执行对某个表的查询,并且该语句的`WHERE`
     #子句中有针对该表的搜索条件时,在`Extra`列中会提示上述额外信息。
     EXPLAIN SELECT * FROM s1 WHERE common_field = 'a';
    
    
     #当使用索引访问来执行对某个表的查询,并且该语句的`WHERE`子句中
    #有除了该索引包含的列之外的其他搜索条件时,在`Extra`列中也会提示上述额外信息。
     EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND common_field = 'a';
    
    
     #当查询列表处有`MIN`或者`MAX`聚合函数,但是并没有符合`WHERE`子句中
    #的搜索条件的记录时,将会提示该额外信息
     EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'abcdefg';
    
     EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'NlPros'; #NlPros 是 s1表中key1字段真实存在的数据
    
    #select * from s1 limit 10;
    
     #当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以
     #使用覆盖索引的情况下,在`Extra`列将会提示该额外信息。比方说下边这个查询中只
     #需要用到`idx_key1`而不需要回表操作:
     EXPLAIN SELECT key1,id FROM s1 WHERE key1 = 'a';
    
    
     #有些搜索条件中虽然出现了索引列,但却不能使用到索引
     #看课件理解索引条件下推
     EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%a';
    
    
    #在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为
    #其分配一块名叫`join buffer`的内存块来加快查询速度,也就是我们所讲的`基于块的嵌套循环算法`
    #见课件说明
    EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field;
    
    
     #当我们使用左(外)连接时,如果`WHERE`子句中包含要求被驱动表的某个列等于`NULL`值的搜索条件,
     #而且那个列又是不允许存储`NULL`值的,那么在该表的执行计划的Extra列就会提示`Not exists`额外信息
     EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;
    
    
     #如果执行计划的`Extra`列出现了`Using intersect(...)`提示,说明准备使用`Intersect`索引
     #合并的方式执行查询,括号中的`...`表示需要进行索引合并的索引名称;
     #如果出现了`Using union(...)`提示,说明准备使用`Union`索引合并的方式执行查询;
     #出现了`Using sort_union(...)`提示,说明准备使用`Sort-Union`索引合并的方式执行查询。
     EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
    
    
     #当我们的`LIMIT`子句的参数为`0`时,表示压根儿不打算从表中读出任何记录,将会提示该额外信息
     EXPLAIN SELECT * FROM s1 LIMIT 0;
    
    
     #有一些情况下对结果集中的记录进行排序是可以使用到索引的。
     #比如:
     EXPLAIN SELECT * FROM s1 ORDER BY key1 LIMIT 10;
    
    
     #很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)
    #进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序(英文名:`filesort`)。
    
     #如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的`Extra`列中显示`Using filesort`提示
     EXPLAIN SELECT * FROM s1 ORDER BY common_field LIMIT 10;
    
    
    #在许多查询的执行过程中,MySQL可能会借助临时表来完成一些功能,比如去重、排序之类的,比如我们
     #在执行许多包含`DISTINCT`、`GROUP BY`、`UNION`等子句的查询过程中,如果不能有效利用索引来完成
    #查询,MySQL很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,在执行
    #计划的`Extra`列将会显示`Using temporary`提示
    EXPLAIN SELECT DISTINCT common_field FROM s1; 
    
    #EXPLAIN SELECT DISTINCT key1 FROM s1;
    
     #同上。
    EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field;
    
    #执行计划中出现`Using temporary`并不是一个好的征兆,因为建立与维护临时表要付出很大成本的,所以
     #我们`最好能使用索引来替代掉使用临时表`。比如:扫描指定的索引idx_key1即可
     EXPLAIN SELECT key1, COUNT(*) AS amount FROM s1 GROUP BY key1;
    
    #json格式的explain
    EXPLAIN FORMAT=JSON SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 
    WHERE s1.common_field = 'a';
    
    

7. EXPLAIN的进一步使用

7.1 EXPLAIN四种输出格式

这里谈谈EXPLAIN的输出格式。EXPLAIN可以输出四种格式:传统格式,JSON格式,TREE格式以及可视化输出。用户可以根据需要选择适用于自己的格式。

  1. 传统格式
    传统格式简单明了,输出是一个表格形式,概要说明查询计划。

    mysql> EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.common_field IS NOT NULL;
    

    image-20220602180402002

  2. JSON格式
    第1种格式中介绍的EXPLAIN语句输出中缺少了一个衡量执行计划好坏的重要属性–成本。而JSON格式是四种格式里面 输出信息最详尽的格式,里面包含了执行的成本信息。

    • JSON格式:在EXPLAIN单词和真正的查询语句中间加上FORMAT=JSON。
      EXPLAIN FORMAT=JSON SELECT ....
      

    EXPLAIN的column与JSON的对应关系:(来源于MySQL 5.7文档)
    image-20220603153428246

    这样我们就可以得到一个 json格式的执行计划,里面包含该计划花费的成本,比如这样:

    mysql> EXPLAIN FORMAT=JSON SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 WHERE s1.common_field = 'a ' \G;
    *************************** 1. row ***************************
    EXPLAIN: {
      "query_block": {
        "select_id": 1,  -- 整个查询语句只有1个SELECT关键字,该关键宁对应的id号为1
        "cost_info": {
          "query_cost": "2174.95"  -- 整个查询的执行成本预计为2174.95
        },
        -- 以下是参与嵌套循环连接算法的各个表的信息
     "nested_loop": [
          {
            "table": {
              "table_name": "s1", -- s1表是驱动表
              "access_type": "ALL", -- 菜访问方法为ALL。意味着使用全表扫描访问可能使用的索引
              "possible_keys": [  -- 可能使用的成本
               "idx_key1"
            ],
           "rows_examined_per_scan": 9895, --查询一次s1表大致需要扫描9895条记录
            "rows_produced_per_join": 989, --驱动表s1的扇出是968
              "filtered": "10.00",  -- condition filtering代.表的百分比
          "cost_info": {
            "read_cost": "987.55",
            "eval_cost": "98.95",
            "prefix_cost": "1086.50", -- 单次查询s1表总共的成本
            "data_read_per_join": "2M" --i读取的数期星
          },
          "used_columns": [
            "id",
            "key1",
            "key2",
            "key3",
            "key_part1",
            "key_part2",
            "key_part3",
            "common_field"
          ],
        -- 对s1表访问时针对单表查询的条件
          "attached_condition": "((`atguigudb1`.`s1`.`common_field` = 'a ') and (`atguigudb1`.`s1`.`key1` is not null))"
        }
      },
      {
        "table": {
          "table_name": "s2", --s2表是被驱动表
          "access_type": "eq_ref", --访问方法为ref.意味者使用索引等值匹配的方式访问
          "possible_keys": [ -- 可能使用的索引
            "idx_key2"
          ],
          "key": "idx_key2", --实际用的索引
          "used_key_parts": [ --使用到的索引列
            "key2"
          ],
          "key_length": "5",  -- key_len
          "ref": [   -- 与key2列进行等值匹配的对象
            "atguigudb1.s1.key1"
          ],
          "rows_examined_per_scan": 1,  --查询一次s2表大致需要扫描1条记录
          "rows_produced_per_join": 989, -- 被驱动表s2的扇出是989(由于后边没有多余的表进行连接,所以这个值没啥用)
          "filtered": "100.00", -- condition filtering代表的百分比
        
        --S2表使用索引进行查询的搜索条件
          "index_condition": "(cast(`atguigudb1`.`s1`.`key1` as double) = cast(`atguigudb1`.`s2`.`key2` as double))",
          "cost_info": {
            "read_cost": "989.50",
            "eval_cost": "98.95",
            "prefix_cost": "2174.95", -- 单次查询s1、多次查询s2表总共的成本
            "data_read_per_join": "1M" -- 读取的数据量
          },
          "used_columns": [ -- 执行查询中涉及到的列
            "id",
            "key1",
            "key2",
            "key3",
            "key_part1",
            "key_part2",
            "key_part3",
            "common_field"
          ]
        }
      }
    ]
      }
    }
    1 row in set, 2 warnings (0.00 sec)
    

    我们使用 # 后边跟随注释的形式为大家解释了 EXPLAIN FORMAT=JSON 语句的输出内容,但是大家可能有疑问 “cost_info” 里边的成本看着怪怪的,它们是怎么计算出来的?先看 s1 表的 “cost_info” 部分:

    "cost_info": {
    	"read_cost": "987.55",
    	"eval_cost": "98.95",
    	"prefix_cost": "1086.50", -- 单次查询s1表总共的成本
    	"data_read_per_join": "2M" --i读取的数期星
    },
    
    • read_cost 是由下边这两部分组成的:

      • IO 成本
      • 检测 rows × (1 - filter) 条记录的 CPU 成本

      小贴士: rows和filter都是我们前边介绍执行计划的输出列,在JSON格式的执行计划中,rows相当于rows_examined_per_scan,filtered名称不变。

    • eval_cost 是这样计算的:检测 rows × filter 条记录的成本。

    • prefix_cost 就是单独查询 s1 表的成本,也就是:read_cost + eval_cost

    • data_read_per_join 表示在此次查询中需要读取的数据量。

    对于 s2 表的 “cost_info” 部分是这样的:

    "cost_info": {
    "read_cost": "989.50",
    "eval_cost": "98.95",
    "prefix_cost": "2174.95", -- 单次查询s1、多次查询s2表总共的成本
    "data_read_per_join": "1M" -- 读取的数据量
    },
    

    由于 s2 表是被驱动表,所以可能被读取多次,这里的 read_cost 和 eval_cost 是访问多次 s2 表后累加起来的值,大家主要关注里边儿的 prefix_cost 的值代表的是整个连接查询预计的成本,也就是单次查询 s1 表和多次查询 s2 表后的成本的和,也就是:

    989.50 + 98.95 + 1086.50 = 2174.95
    
  3. TREE格式
    TREE格式是8.0.16版本之后引入的新格式,主要根据查询的各个部分之间的关系和各部分的执行顺序来描述如何查询。

    mysql> EXPLAIN FORMAT=tree SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 WHERE s1.common_field = 'a' \G;
    *************************** 1. row ***************************
    EXPLAIN: -> Nested loop inner join  (cost=1360.08 rows=990)
        -> Filter: ((s1.common_field = 'a') and (s1.key1 is not null))  (cost=1013.75 rows=990)
        -> Table scan on s1  (cost=1013.75 rows=9895)
        -> Single-row index lookup on s2 using idx_key2 (key2=s1.key1), with index condition: (cast(s1.key1 as double) = cast(s2.key2 as double))  (cost=0.25 rows=1)
        
    1 row in set, 1 warning (0.00 sec)
    
  4. 可视化输出
    可视化输出,可以通过MySQL Workbench可视化查看MySQL的执行计划。通过点击Workbench的放大镜图标,即可生成可视化的查询计划。

    image-20220603161454537

    上图按从左到右的连接顺序显示表。红色框表示 全表扫描 ,而绿色框表示使用 索引查找 。对于每个表,显示使用的索引。还要注意的是,每个表格的框上方是每个表访问所发现的行数的估计值以及访问该表的成本。

7.2 SHOW WARNINGS的使用

在我们使用 EXPLAIN 语句查看了某个查询的执行计划后,紧接着还可以使用SHOW WARNINGS 语句查看与这个查询的执行计划有关的一些扩展信息,比如这样:

mysql> EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.common_field IS NOT NULL;

image-20220602181707163

# 查看优化后的执行语句
mysql> SHOW WARNINGS\G;
*************************** 1. row ***************************
  Level: Warning
   Code: 1739
Message: Cannot use ref access on index 'idx_key1' due to type or collation conversion on field 'key1'
*************************** 2. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select `atguigudb1`.`s1`.`key1` AS `key1`,`atguigudb1`.`s2`.`key1` AS `key1` from `atguigudb1`.`s1` join `atguigudb1`.`s2` where ((`atguigudb1`.`s2`.`common_field` is not null) and (`atguigudb1`.`s1`.`key1` = `atguigudb1`.`s2`.`key1`))
2 rows in set (0.00 sec)

大家可以看到 SHOW WARNINGS 展示出来的信息有三个字段,分别是 Level 、Code 、Message 。我们最常见的就是Code为1003的信息,当Code值为1003时,Message字段展示的信息类似于查询优化器将我们的查询语句重写后的语句。比如我们上边的查询本来是一个左(外)连接查询,但是有一个s2.common_field IS NOT NULL的条件,这就会导致查询优化器把左(外)连接查询优化为内连接查询,从 SHOW WARNINGS 的 Message 字段也可以看出来,原本的 LEFT JOIN 已经变成了JOIN。

8. 分析优化器执行计划:trace

OPTIMIZER_TRACE 是MySQL 5.6引入的一项跟踪功能,它可以跟踪优化器做出的各种决策(比如访问表的方法、各种开销计算、各种转换等),并将跟踪结果记录到 INFORMATION_SCHENA. OPTIMIZER_TRACE 表中。

此功能默认关闭。开启trace,并设置格式为JSON,同时设置trace最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能够完整展示。

# 开启
SET optimizer_trace="enabled=on",end_markers_in_json=on; 
# 设置大小
set optimizer_trace_max_mem_size=1000000;
 
# 使用
select * from student where id < 10;
select * from information_schema.optimizer_trace\G

开启后,可分析如下语句:

  • SELECT
  • INSERT
  • REPLACE
  • UPDATE
  • DELETE
  • EXPLAIN
  • SET
  • DECLARE
  • CASE
  • IF
  • RETURN
  • CALL

测试:执行如下SQL语句

select * from student where id < 10;

最后, 查询 information_schema.optimizer_trace 就可以知道MySQL是如何执行SQL的 :

select * from information_schema.optimizer_trace\G
*************************** 1. row ***************************
 //第1部分:查询语句
 QUERY: select * from student where id < 10
 //第2部分:QUERY字段对应语句的跟踪信息
 TRACE: {
 "steps": [
 {
   "join_preparation": {  //预备工作
    "select#": 1,
    "steps": [
    {
      "expanded_query": "/* select#1 */ select `student`.`id` AS
`id`,`student`.`stuno` AS `stuno`,`student`.`name` AS `name`,`student`.`age` AS
`age`,`student`.`classId` AS `classId` from `student` where (`student`.`id` < 10)"
    }
   ] /* steps */
  } /* join_preparation */
 },
 {
   "join_optimization": {  //进行优化
    "select#": 1,
    "steps": [
    {
      "condition_processing": {  //条件处理
       "condition": "WHERE",
       "original_condition": "(`student`.`id` < 10)",
       "steps": [
       {
         "transformation": "equality_propagation",
         "resulting_condition": "(`student`.`id` < 10)"
       },
       {
         "transformation": "constant_propagation",
         "resulting_condition": "(`student`.`id` < 10)"
       },
       {
         "transformation": "trivial_condition_removal",
         "resulting_condition": "(`student`.`id` < 10)"
       }
             ] /* steps */
     } /* condition_processing */
    },
    {
      "substitute_generated_columns": {  //替换生成的列
     } /* substitute_generated_columns */
    },
    {
      "table_dependencies": [   //表的依赖关系
      {
        "table": "`student`",
        "row_may_be_null": false,
        "map_bit": 0,
        "depends_on_map_bits": [
       ] /* depends_on_map_bits */
      }
     ] /* table_dependencies */
    },
    {
      "ref_optimizer_key_uses": [   //使用键
     ] /* ref_optimizer_key_uses */
    },
    {
      "rows_estimation": [   //行判断
      {
        "table": "`student`",
        "range_analysis": {
         "table_scan": {
          "rows": 3973767,
          "cost": 408558
        } /* table_scan */,   //扫描表
         "potential_range_indexes": [   //潜在的范围索引
         {
           "index": "PRIMARY",
           "usable": true,
           "key_parts": [
            "id"
          ] /* key_parts */
         }
        ] /* potential_range_indexes */,
         "setup_range_conditions": [   //设置范围条件
        ] /* setup_range_conditions */,
         "group_index_range": {
          "chosen": false,
          "cause": "not_group_by_or_distinct"
        } /* group_index_range */,
         "skip_scan_range": {
          "potential_skip_scan_indexes": [
          {
            "index": "PRIMARY",
            "usable": false,
            "cause": "query_references_nonkey_column"
          }
         ] /* potential_skip_scan_indexes */
        } /* skip_scan_range */,
         "analyzing_range_alternatives": {  //分析范围选项
          "range_scan_alternatives": [
          {
              "index": "PRIMARY",
            "ranges": [
             "id < 10"
           ] /* ranges */,
            "index_dives_for_eq_ranges": true,
            "rowid_ordered": true,
            "using_mrr": false,
            "index_only": false,
            "rows": 9,
            "cost": 1.91986,
            "chosen": true
          }
         ] /* range_scan_alternatives */,
          "analyzing_roworder_intersect": {
           "usable": false,
           "cause": "too_few_roworder_scans"
         } /* analyzing_roworder_intersect */
        } /* analyzing_range_alternatives */,
         "chosen_range_access_summary": {   //选择范围访问摘要
          "range_access_plan": {
           "type": "range_scan",
           "index": "PRIMARY",
           "rows": 9,
           "ranges": [
            "id < 10"
          ] /* ranges */
         } /* range_access_plan */,
          "rows_for_plan": 9,
          "cost_for_plan": 1.91986,
          "chosen": true
        } /* chosen_range_access_summary */
       } /* range_analysis */
      }
     ] /* rows_estimation */
    },
    {
      "considered_execution_plans": [  //考虑执行计划
      {
        "plan_prefix": [
       ] /* plan_prefix */,
        "table": "`student`",
        "best_access_path": {  //最佳访问路径
         "considered_access_paths": [
         {
           "rows_to_scan": 9,
           "access_type": "range",
           "range_details": {
            "used_index": "PRIMARY"
          } /* range_details */,
           "resulting_rows": 9,
           "cost": 2.81986,
           "chosen": true
         }
        ] /* considered_access_paths */
       } /* best_access_path */,
        "condition_filtering_pct": 100,  //行过滤百分比
        "rows_for_plan": 9,
        "cost_for_plan": 2.81986,
                                     "chosen": true
      }
     ] /* considered_execution_plans */
    },
    {
      "attaching_conditions_to_tables": {  //将条件附加到表上
       "original_condition": "(`student`.`id` < 10)",
       "attached_conditions_computation": [
      ] /* attached_conditions_computation */,
       "attached_conditions_summary": [  //附加条件概要
       {
         "table": "`student`",
         "attached": "(`student`.`id` < 10)"
       }
      ] /* attached_conditions_summary */
     } /* attaching_conditions_to_tables */
    },
    {
      "finalizing_table_conditions": [
      {
        "table": "`student`",
        "original_table_condition": "(`student`.`id` < 10)",
        "final_table_condition  ": "(`student`.`id` < 10)"
      }
     ] /* finalizing_table_conditions */
    },
    {
      "refine_plan": [  //精简计划
      {
        "table": "`student`"
      }
     ] /* refine_plan */
    }
   ] /* steps */
  } /* join_optimization */
 },
 {
   "join_execution": {   //执行
    "select#": 1,
    "steps": [
   ] /* steps */
  } /* join_execution */
 }
] /* steps */
}
 
//第3部分:跟踪信息过长时,被截断的跟踪信息的字节数。
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0  //丢失的超出最大容量的字节
//第4部分:执行跟踪语句的用户是否有查看对象的权限。当不具有权限时,该列信息为1且TRACE字段为空,一般在
调用带有SQL SECURITY DEFINER的视图或者是存储过程的情况下,会出现此问题。
INSUFFICIENT_PRIVILEGES: 0  //缺失权限
1 row in set (0.00 sec)

9. MySQL监控分析视图-sys schema

关于MySQL的性能监控和问题诊断,我们一般都从performance_schema中去获取想要的数据,在MySQL5.7.7版本中新增sys schema,它将performance_schema和information_schema中的数据以更容易理解的方式总结归纳为"视图”,其目的就是为了 降低查询performance_schema的复杂度,让DBA能够快速的定位问题。下面看看这些库中都有哪些监控表和视图,掌握了这些,在我们开发和运维的过程中就起到了事半功倍的效果。

9.1 Sys schema视图摘要

  1. 主机相关:以host_summary开头,主要汇总了IO延迟的信息。
  2. Innodb相关:以innodb开头,汇总了innodb buffer信息和事务等待innodb锁的信息。
  3. I/O相关:以io开头,汇总了等待I/O、I/O使用量情况。
  4. 内存使用情况:以memory开头,从主机、线程、事件等角度展示内存的使用情况
  5. 连接与会话信息:processlist和session相关视图,总结了会话相关信息。
  6. 表相关:以schema_table开头的视图,展示了表的统计信息。
  7. 索引信息:统计了索引的使用情况,包含冗余索引和未使用的索引情况。
  8. 语句相关:以statement开头,包含执行全表扫描、使用临时表、排序等的语句信息。
  9. 用户相关:以user开头的视图,统计了用户使用的文件I/O、执行语句统计信息。
  10. 等待事件相关信息:以wait开头,展示等待事件的延迟情况。

9.2 Sys schema视图使用场景

索引情况

#1. 查询冗余索引 
select * from sys.schema_redundant_indexes; 
#2. 查询未使用过的索引 
select * from sys.schema_unused_indexes; 
#3. 查询索引的使用情况 
select index_name,rows_selected,rows_inserted,rows_updated,rows_deleted from sys.schema_index_statistics where table_schema='dbname' ;

表相关

# 1. 查询表的访问量 
select table_schema,table_name,sum(io_read_requests+io_write_requests) as io from sys.schema_table_statistics group by table_schema,table_name order by io desc; 
 
# 2. 查询占用buffer pool较多的表 
select object_schema,object_name,allocated,data
from sys.innodb_buffer_stats_by_table order by allocated limit 10; 
 
# 3. 查看表的全表扫描情况 
select * from sys.statements_with_full_table_scans where db='dbname';

语句相关

#1. 监控SQL执行的频率 
select db,exec_count,query from sys.statement_analysis order by exec_count desc; 
 
#2. 监控使用了排序的SQL 
select db,exec_count,first_seen,last_seen,query
from sys.statements_with_sorting limit 1; 
 
#3. 监控使用了临时表或者磁盘临时表的SQL 
select db,exec_count,tmp_tables,tmp_disk_tables,query
from sys.statement_analysis where tmp_tables>0 or tmp_disk_tables >0 order by (tmp_tables+tmp_disk_tables) desc;

IO相关

#1. 查看消耗磁盘IO的文件 
select file,avg_read,avg_write,avg_read+avg_write as avg_io
from sys.io_global_by_file_by_bytes order by avg_read limit 10;

Innodb 相关

#1. 行锁阻塞情况 
select * from sys.innodb_lock_waits;

风险提示:
通过sys库去查询时,MySQL会 消耗大量资源 去收集相关信息,严重的可能会导致业务请求被阻塞,从而引起故障。建议生产上 不要频繁 的去查询sys或者performance_schema、information_schema来完成监控、巡检等工作。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MySQL高级篇_第09章_性能分析工具的使用 的相关文章

  • MySQL Connector C/C API - 使用特殊字符进行查询

    我是一个 C 程序 我有一个接受域名参数的函数 void db domains query char name 使用 mysql query 我测试数据库中是否存在域名 如果不是这种情况 我插入新域名 char query 400 spri
  • MySQL连接字符集问题

    我在 Mac 上使用带有 MySQL 的 velosurf 没有任何编码问题 但是当我切换到 Linux 计算机时 从 velosurf 获得的值未正确编码 我发现这可能是默认连接字符集的问题 在 Mac 上我得到 mysql gt sho
  • 在服务器上找不到本地主机或 phpMyAdmin:如何修复?

    我按照安装说明进行操作PHP MySQL and PHPMyAdmin 但是当我尝试访问时http localhost phpmyadmin 我收到此错误 未找到 在此找不到请求的 URL phpmyadmin 服务器 然后我尝试访问loc
  • 即使使用“autoReconnect=true”,MySql JDBC 也会超时[重复]

    这个问题在这里已经有答案了 有时 我的 Java Tomcat6 Debian Squeeze 应用程序无法与 MySql 服务器通信 Tomcat 应用程序位于前端服务器上 而 MySql 位于单独的 仅限 MySql 的机器上 一个典型
  • 比特纳米。重置mysql根密码

    我如何重置 MySQL 中的 root 密码和帐户 因为我按照如何为其他服务器授予权限的说明操作 并且意外地将 root 用户 Mysql 绑定到其他 IP 地址 现在看来我无法在 localhost 上以管理员身份登录 Thanks 您有
  • Flask-login:无法理解它是如何工作的

    我试图理解如何Flask Login https flask login readthedocs org en latest works 我在他们的文档中看到他们使用预先填充的用户列表 我想使用数据库存储的用户列表 但是 我不明白其中的一些
  • Java JDBC:更改表

    我希望对此表进行以下修改 添加 状态列 varchar 20 日期列 时间戳 我不确定该怎么做 String createTable Create table aircraft aircraftNumber int airLineCompa
  • INSERT..RETURNING 在 JOOQ 中不起作用

    我有一个 MariaDB 数据库 我正在尝试在表中插入一行users 它有一个生成的id我想在插入后得到它 我见过this http www jooq org doc 3 8 manual sql building sql statemen
  • 如何从shell脚本自动登录MySQL?

    我有一个 MySQL 服务器 其中有一个用户和密码 我想在 shell 脚本中执行一些 SQL 查询而不指定密码 如下所示 config sh MYSQL ROOT root MYSQL PASS password mysql sh sou
  • 在 MySQL 中存储表情符号的编码问题:如何使用 Prisma ORM 在 NodeJS 中定义字符排序规则?

    亲爱的 Nodejs 专家和数据库专家 我们在 MySQL 数据库中存储表情符号和其他特殊字符时遇到问题 我们使用 Prisma 得到一个错误 这是我们使用的 ORM 参数无法从排序规则 utf8 general ci 转换为 utf8mb
  • POINT 列上的 MySQL INSERT/UPDATE

    我正在尝试用我国家的地理位置填充我的数据库 我的一张表有 4 个字段 ID PK 纬度 经度和地理点 EDIT SCDBs Punto Geografico SET lat 18 469692 SET lon 63 93212 SET g
  • 显示标准化数据

    跟进问题 添加 2 个不同表的总和 https stackoverflow com questions 39717541 adding sum from 2 different tables 我创建了3个表 members videos v
  • MySql - 复制监控工具[关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我有一个主 从 MySql 复制 我正在寻找一个允许我监视复制的工具 查看它没有错误 检查滞后等 我更喜
  • 在 C# 中,当有人插入、删除或修改记录时,如何从 MySQL 获取事件?

    我正在 WPF Net 中开发一个程序 我需要知道何时有人对数据库的任何表进行更改 这个想法是在数据库发生更改时从数据库接收一个事件 我读了很多文章 但找不到解决我的问题的方法 亲切的问候 最好的解决方案是使用消息队列 在您的应用程序向数据
  • 连接 Netbeans 和 MySQL 但出现大整数错误

    所以我正在尝试向我的 Netbeans 数据库 即 MySQL 添加新连接 但我遇到了大整数转换错误 有人可以帮助我吗 详细地 我右键单击现有的MySQL 服务器位于 localhost 3306 root 已断开连接 gt gt 选择co
  • MySQL 和 Hibernate 之间的主键自增由谁负责?

    MySQL CREATE TABLE role id role INT 11 unsigned NOT NULL AUTO INCREMENT PRIMARY KEY id role AUTO INCREMENT 1 休眠 Entity p
  • covertJSONtoSQL 在 NiFi 中返回空值

    我正在设计一项工作 使用以下命令将数据从 MySQL 中的数据库转移到另一个数据库 MySQL 执行SQL处理器随后将Avro转换为Json then 将Json转换为SQL then PutSQL如下流程图所示 将JSON转换为SQL返回
  • mysql转储到derby

    我正在使用 derby 在 eclipse 中进行开发 是否可以从 MySQL 转储表并以某种方式将其用于 derby 我知道 ddl 和 dml 对于两个 dbms 来说是不同的 但我正在寻找一种除了转储 导出之外的合适方法 我可以找到两
  • 错误代码:1305。函数或过程不存在

    因此 我在 MySQL 中创建一个函数 然后尝试向用户授予使用该函数的权限 但我无法这样做 这就是我正在做的 DELIMITER USE rxhelp36 scbn DROP FUNCTION IF EXISTS businessDayDi
  • 在 jQuery AJAX 成功中从 MySql 获取特定响应

    好吧 我有这个 ajax 代码 它将在 Success 块中返回 MySql 的结果 ajax type POST url index php success function data alert data My Query sql SE

随机推荐

  • 前端下载二进制流文件

    前端下载文件有两种方法 第一种 后台直接返回下载流格式的文件地址 前端用a标签新开窗口或者window open 下载即可 另外一种 后台返回二进制流文件内容 前端通过blob进行转化后再下载 具体代码 async downloadFn t
  • pyautogui.locateOnScreen()扫描屏幕返回none问题

    我们有一张这样的图片 我们想用pyautgui模块的locateOnScreen 定位该图片在屏幕对应的位置 发现屏幕中明明存在改图片 但是扫描失败 找不到该图片 如下两张图 我们给locateOnScreen 括号里添加confidenc
  • Linux——主函数的三个参数,printf方法的隐藏缓冲区

    主函数的三个参数 在windows下 c语言的主函数默认有两个参数 很多人会惊奇 这是什么 好像从没有听说过 我们在写C代码的时候都是习惯性的新建空项目然后自己添加 cpp文件 但是可能有部分人习惯直接新建控制台程序 然后可能就会看到这么一
  • 对你的屁股好一点!

    作为软件开发人员 买一把上乘的电脑椅也许是你能做的最明智的投资之一 事实上 在过去几年看过各种各样的椅子之后 我得出了一个结论 如果你想买一把上乘的椅子 你要花的钱不会少于500美元 如果你在座椅上花的钱没那么多 除非你正在经历那场跨世纪互
  • 技术博客写作「个人经验分享」

    技术博客写作 个人经验分享 仔细想来 从19年我刚开始试着技术写作算起 已经过去了好几年时间 刚好趁着这次的 赠送奖牌活动 奖牌很好看 我很想要hhh 来分享一下我关于技术博客写作的一些个人经验 文章目录 技术博客写作 个人经验分享 Wha
  • 新装机电脑网速特别慢

    老主机最近有些问题 我一直没能解决 也用了5年了 索性出给闲鱼二道贩子 又自己新配置了一台主机 具体表现为 主机插网线后 跟之前主机比 浏览网页或者下载资源速度特别慢 打开百度浏览器都要转上一会 不管下载什么资源 下载速度最快只有100 2
  • Redis面试题整理

    1 什么是Redis以及Redis的优缺点 Redis是一个非关系型数据库 NO SQL 类似于Java中Map 其中key为字符串类型 Redis支持的Value类型有5种 字符串 String 列表 List 集合 set 散列表 Ha
  • 【Python技巧】python字符串编码全是Unicode,Unicode压缩到utf-8,encode和decode,bytes

    一 Python3字符串全是Unicode 这意味着 只要用python3 x 无论我们的程序以那种语言开发 都可以在全球各国电脑上正常显示 python3 x中 把字符串变成了unicode 文件默认编码为utf 8 unicode 分为
  • Acwing 893. 集合-Nim游戏

    Mex运算 设S表示一个非负整数集合 定义mex S 为求出不属于集合S的最小非负整数的运算 即 mex S min x x属于自然数 且x不属于S SG函数 在有向图游戏中 对于每个节点x 设从x出发共有k条有向边 分别到达节点y1 y2
  • Linux文件恢复

    0 前言 用rm命令多了 难免会出现误删文件的情况 本人就在Ubuntu14 04中做项目时遇到该问题 本来想使用rm命令删除以14开头的文件 结果写成如下形式 导致文件被误删 rm 14 14 和 误增了一个空格 1 工具 ext3和ex
  • C语言程序设计基础OJ练习题(实验六一维数组)

    一 C语言实验 最值 Time Limit 1000 ms Memory Limit 65536 KiB Submit Statistic Problem Description 有一个长度为n的整数序列 其中最大值和最小值不会出现在序列的
  • 网站接入CDN显示不正常无法加载HTTPS样式表

    网站接入腾讯云内容分发网络CDN后 显示不正常 网站加载的CSS样式表HTTP协议 无法加载HTTPS 如何解决 解决方法 修改回源协议为HTTPS即可 腾讯云CDN网站HTTPS样式表无法载入的解决方法 网站接入腾讯云CDN后 网站显示不
  • IOCTL命令号

    IOXX是驱动IOCTL命令号的宏转换定义 用于对命令进行分类 防止不同类驱动程序具有相同命令号 导至误打开驱动程序而驱动程序功能调用被误操作 命令码的组织是有一些讲究的 因为我们一定要做到命令和设备是一一对应的 这样才不会将正确的命令发给
  • 记一次生产环境tomcat线程数打满情况分析

    前言 旨在分享工作中遇到的各种问题及解决思路与方案 与大家一起学习 学无止境 加油 Just do it 问题描述 运行环境描述 tomcat 8 5 单节点 该应用集群20个节点 avg tps 250 max tps 350 tomca
  • HDFS客户端写流程

    HDFS客户端写流程 1 创建文件 首先调用DistributedFileSystem creat 创建一个空的HDFS文件 然后这个方法在底层会通过调用ClientProtocol create 方法通知Namenode执行对应的操作 N
  • Flutter开发之——单组件布局容器-FittedBox

    一 概述 当子组件的宽高比和父组件的宽高比不一样时 我们等比拉伸或者填充父组件 这时我们可以使用FittedBox 二 FittedBox 2 1 构造方法 const FittedBox Key key this fit BoxFit c
  • 区块链学习——区块链的技术栈

    摘要 我在区块链学习的上一篇博文 链接 区块链学习 区块链技术理念与工作流程中 简单介绍了区块链的技术理念以及工作流程 本文我将继续介绍区块链技术栈 我们知道 区块链本身只是一个数据的记录格式 就像们平时使用Excel表格 Word文档一样
  • Java #{}和${}区别

    Java 和 区别 Mybatis中使用 可以防止sql注入 表示一个占位符号 实现向PreparedStatement占位符中设置值 表示一个占位符 自动进行Java类型到JDBC类型的转换 因此 可以有效防止SQL注入 可以接收简单类型
  • 过程改进的关注点之项目管理过程

    从项目估算到项目策划 再到计划跟踪控制 包括风险的识别与管理 常见的改进点有哪些呢 基于我的咨询经验梳理如下 小类 关注点 估算方法 无论是采用经验法还是模型法 都要根据历史的估算偏差率评价估算方法的合理性 如果偏差率大 则应该识别改进点
  • MySQL高级篇_第09章_性能分析工具的使用

    1 数据库服务器的优化步骤 当我们遇到数据库调优问题的时候 该如何思考呢 这里把思考的流程整理成下面这张图 整个流程划分成了 观察 Show status 和 行动 Action 两个部分 字母 S 的部分代表观察 会使用相应的分析工具 字