第18天SQL进阶-查询优化- SHOW PROFILE(SQL 小虚竹)

Posted 小虚竹

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第18天SQL进阶-查询优化- SHOW PROFILE(SQL 小虚竹)相关的知识,希望对你有一定的参考价值。

回城传送–》《32天SQL筑基》

文章目录

零、前言

今天是学习 SQL 打卡的第 18 天,每天我会提供一篇文章供群成员阅读( 不需要订阅付钱 )。

希望大家先自己思考,如果实在没有想法,再看下面的解题思路,自己再实现一遍。在小虚竹JAVA社区 中对应的 【打卡贴】打卡,今天的任务就算完成了,养成每天学习打卡的好习惯。

​ 虚竹哥会组织大家一起学习同一篇文章,所以有什么问题都可以在群里问,群里的小伙伴可以迅速地帮到你,一个人可以走得很快,一群人可以走得很远,有一起学习交流的战友,是多么幸运的事情。

​ 我的学习策略很简单,题海策略+ 费曼学习法。如果能把这些题都认认真真自己实现一遍,那意味着 SQL 已经筑基成功了。后面的进阶学习,可以继续跟着我,一起走向架构师之路。

今天的学习内容是:SQL进阶-查询优化- SHOW PROFILE

一、练习题目

题目链接难度
SQL进阶-查询优化- SHOW PROFILE★★★☆☆

二、SQL思路

SQL进阶-查询优化- SHOW PROFILE


初始化数据

这里写入初始化表结构,初始化数据的sql

解法

SHOW PROFILE是什么

mysql5.0.37 版本开始支持 SHOW PROFILES语句和SHOW PROFILE语句。
**Show Profile **是 MySQL 提供的可以用来分析当前查询 SQL 语句执行的资源消耗情况的工具,可用于 SQL 调优的测量。默认情况下处于关闭状态,开启会消耗一定的性能,一般在 SQL 分析和优化的时候使用。

MySQL5.7 中, show profile 命令已经开始不推荐使用,MySQL使用performance_schema 中系统表的信息来替代show profile命令。

但还是有必要学习的,企业可能存在一些老系统还在使用mysql7之前的版本。

开启/关闭 PROFILE

查看MySQL是否支持PROFILE:

SELECT @@have_profiling;


结果显示,当前MySQL支持PROFILE。
查看MySQL的profiling是否关闭的:

SELECT @@profiling; 


现在是开启的,如果值为0 ,是关闭的。
如果是关闭的,可以通过SET语句开启profiling。

SET SESSION profiling = 1;

当前会话开启,关闭会话配置失效。
如果需要关闭profiling的状态,命令如下:

SET SESSION profiling = 0;

分析InnoDB数据表

初始化数据

创建存储引擎为InnoDB的学生表

drop table if exists student;
CREATE TABLE `student` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`order_num` long NOT NULL  COMMENT '序号',
`student_name` varchar(20) NOT NULL COMMENT '姓名',
`age` int COMMENT '年龄',
 PRIMARY KEY (`id`)
 )ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;


插入100万条数据,这里使用了存储过程,进行批量提交数据,先关闭自动提交,插入一定条数再进行提交。

--创建存储过程

drop procedure if exists add_student;

CREATE  PROCEDURE `add_student`(in n int,in batchNum int) 
BEGIN
    DECLARE i INT DEFAULT 1;
		
	WHILE (i < n+1 ) DO
	
	set autocommit = 0;
		INSERT into student (order_num,student_name,age) VALUES (i,concat('student_name',i),20);
		set i=i+1;
		if i mod batchNum = 0 then
                commit;
     end if;
	END WHILE;
	 commit;
END

-- 调用
CALL add_student(1000000,100000) 

只需要148s

性能分析

查询学生条数

select count(*)
from student

使用SHOW PROFILES语句查看SQL语句信息。

SHOW PROFILES


注:显示的条数默认是15条,显示的记录数由变量:profiling_history_size 控制。

查询学生条数的Query ID为2000035,查看SQL语句执行过程中所在线程的具体信息:

SHOW PROFILE FOR QUERY 2000035


执行SQL语句的时间主要花费在executing上。

SHOW PROFILE语句支持选择ALL、CPU、BLOCK IO、CONTEXT SWITCH和PAGE FAULTS等来查看具体的明细信息。

使用语法:

SHOW PROFILE type FOR QUERY Query ID

type可写值选项:

ALL: 显示所有的开销信息
BLOCK IO : 显示块IO相关开销
CONTEXT SWITCHS: 上下文切换相关开销
CPU : 显示cpu 相关开销
IPC: 显示发送和接收相关开销
MEMORY: 显示内存相关开销
PAGE FAULTS:显示页面错误相关开销信息
SOURCE : 显示和Source_function ,Source_file,Source_line 相关的开销信息
SWAPS:显示交换次数相关的开销信息
Status : sql 语句执行的状态
Duration: sql 执行过程中每一个步骤的耗时
CPU_user: 当前用户占有的cpu
CPU_system: 系统占有的cpu
Block_ops_in : I/O 输入
Block_ops_out : I/O 输出

SHOW PROFILE all FOR QUERY 2000035



行的解释:

+---------------------+--------------+------+-----+----------+-------+
| Field               | Type         | Null | Key | Default  | Extra |
+---------------------+--------------+------+-----+----------+-------+
| QUERY_ID            | int(20)      | NO   |     | 0        |       | # 语句ID
| STATE               | varchar(30)  | NO   |     |          |       | # 状态
| DURATION            | decimal(9,6) | NO   |     | 0.000000 |       | # 持续时间,单位s
| CPU_USER            | decimal(9,6) | YES  |     | NULL     |       | # 用户态CPU时间,单位s
| CPU_SYSTEM          | decimal(9,6) | YES  |     | NULL     |       | # 系统态CPU时间,单位s
| CONTEXT_VOLUNTARY   | int(20)      | YES  |     | NULL     |       | # 自愿上下文切换次数
| CONTEXT_INVOLUNTARY | int(20)      | YES  |     | NULL     |       | # 非自愿上下文切换次数
| BLOCK_OPS_IN        | int(20)      | YES  |     | NULL     |       | # 块输入次数
| BLOCK_OPS_OUT       | int(20)      | YES  |     | NULL     |       | # 块输出次数
| MESSAGES_SENT       | int(20)      | YES  |     | NULL     |       | # 发送的消息数量
| MESSAGES_RECEIVED   | int(20)      | YES  |     | NULL     |       | # 接收的消息数量
| PAGE_FAULTS_MAJOR   | int(20)      | YES  |     | NULL     |       | # 主要页面错误数量
| PAGE_FAULTS_MINOR   | int(20)      | YES  |     | NULL     |       | # 次要页面错误数量
| SWAPS               | int(20)      | YES  |     | NULL     |       | # 交换次数
| SOURCE_FUNCTION     | varchar(30)  | YES  |     | NULL     |       | # 源代码函数
| SOURCE_FILE         | varchar(20)  | YES  |     | NULL     |       | # 源代码文件
| SOURCE_LINE         | int(20)      | YES  |     | NULL     |       | # 源代码行数
+---------------------+--------------+------+-----+----------+-------+

列的解释:


* Sending data (最重要的一个过程★★★★★)
  线程正在读取和处理一条SELECT语句的行,并且将数据发送至客户端。由于在此期间会执行大量   的磁盘访问(读操作),
  这个状态在一个指定查询的生命周期中经常是耗时最长的。
  这个字段才是SQL真正运行采集+相应数据的时间,而非executing;
  
  --以下按照首字母顺序依次排序
  
* After create
  这个状态当线程创建一个表(包括内部临时表)时,在这个建表功能结束时出现。即使某些错误导致建表失败,也会使用这个状态。

* Analyzing
  当计算MyISAM表索引分布时。(比如进行ANALYZE TABLE时)

* checking permissions
  这个线程检查服务器是否有具有执行该语句的所需权限。

* Checking table
  线程正在执行表检查操作。

* cleaning up
  线程处理一个命令,并正准备释放内存和重置某些状态变量。

* closing tables
  线程正在将变更的表中的数据刷新到磁盘上并正在关闭使用过的表。这应该是一个快速的操作。如果不是这样的话
  则应该检查硬盘空间是否已满或者硬盘IO是否达到瓶颈。 

* converting HEAP to MyISAM
  线程将一个内部临时表转换为磁盘上的MyISAM表。
诊断优化建议:可以调整参数tmp_table_size和max_heap_table_size的值。

* copy to tmp table 
  线程正在处理一个ALTER TABLE语句。这个状态发生在新的表结构已经创建之后,但是在数据被复制进入之前。

* Copying to group table
  如果一个语句有不同的ORDER BYGROUP BY条件,数据会被复制到一个临时表中并且按组排序。

* Copying to tmp table
  线程将数据写入内存中的临时表。  正在创建临时表以存放部分查询结果

* Copying to tmp table on disk
  线程正在将数据写入磁盘中的临时表。临时表的结果集过大。所以线程将临时表由基于内存模式改为基于磁盘模式,以节省内存。
  但是这个过程会异常的缓慢!!
诊断优化建议:可以调整参数tmp_table_size和max_heap_table_size的值。

* Creating index
  线程正在对一个MyISAM表执行ALTER TABLE ... ENABLE KEYS语句。

* Creating sort index
  线程正在使用内部临时表处理一个SELECT 操作。
诊断优化建议:可创建适当的索引。

* creating table
  线程正在创建一个表,包括创建临时表。

* Creating tmp table
  线程正在创建一个临时表在内存或者磁盘上。
  如果这个表创建在内存上但是之后被转换到磁盘上,这个状态在运行Copying to tmp table on disk 的时候保持。
诊断优化建议:判断临时表产生的原因。

* deleting from main table
  线程正在执行多表删除的第一部分,只从第一个表中删除。并且保存列和偏移量用来从其他(参考)表删除。

* deleting from reference tables
  线程正在执行多表删除的第二部分,并从其他表中删除匹配的行。

* discard_or_import_tablespace
  线程正在执行ALTER TABLE ... DISCARD TABLESPACEALTER TABLE ... IMPORT TABLESPACE语句。

* end
  这个状态出现在结束时,但是在对ALTER TABLE, CREATE VIEW, DELETE, INSERT, SELECT,   或者 UPDATE 语句进行清理之前。

* executing
  该线程已开始执行一条语句。

* Execution of init_command
  线程正在执行处于init_command系统变量的值中的语句。

* freeing items
* 线程已经执行了命令。在这个状态中涉及的查询缓存可以得到一些释放。这个状态通常后面跟随cleaning up状态。

* Flushing tables
  线程正在执行FLUSH TABLES 并且等待所有线程关闭他们的表。

* FULLTEXT initialization
  服务器正在准备进行自然语言全文检索。

* init
  这个状态出现在线程初始化ALTER TABLE, DELETE, INSERT, SELECT,UPDATE语句之前。
  服务器在这种状态下进行的操作,包括:刷新全日志、Innodb日志,和一些查询缓存清理操作。

* Killed
  程序对线程发送了KILL语句,并且它应该放弃下一次对KILL标记的检查。
  这个标记在每一个MySQL的主要循环中被检查,但在某些情况下,它可能需要令线程在很短的时间内死亡。
  如果这个线程被其他线程锁住了,这个KILL操作会在其他线程释放锁的瞬时执行。


* logging slow query
  这个线程正在将语句写入慢查询日志。

* NULL
  没有操作的状态。

* login
  线程连接的初始状态。直到客户端已经成功验证。

* manage keys
  服务器启用或禁用表索引。

* Opening tables, Opening table
  线程正试图打开一张表

* optimizing
  服务器执行查询的初步优化。

* preparing
 在查询优化过程中出现这个状态。

* Purging old relay logs
  线程正在移除不必要的中继日志文件。

* query end
  这个状态出现在处理一个查询之后,但是在freeing items状态之前。

* Reading from net
  服务器正在从网络阅读数据包。

* Removing duplicates
  查询正在使用SELECT DISTINCT,这种情况下MySQL不能在早期阶段优化掉一些distinct操作。
  因此,MySQL需要一个额外的阶段,在将结果发送到客户端之前删除所有重复的行。

* removing tmp table
  线程正在移除一个内置临时表,在执行一条SELECT语句之后。 如果没有临时表产生,那么这个状态不被使用。

* rename
* 线程正在重命名一张表。

* rename result table
  线程正在处理ALTER TABLE语句,创建新的表,并且重命名它来代替原有的表。

* Reopen tables
  线程获得了表锁,但是在取得表锁之后才发现该表的底层结构已经发生了变化。线程释放这个锁,关闭表,并试图重新打开该表。

* Repair by sorting
  修复代码正在使用一个分类来创建索引。

* Repair done
  线程完成一个多线程的MyISAM表的修复。

* Repair with keycache
  修复代码正在通过索引缓存一个接一个地使用创建索引。这比通过分类修复要慢很多。

* Rolling back
  线程正在回滚一个事务

* Searching rows for update
  线程正在进行第一阶段,在更新前寻找所有匹配的行。如果update正在更改用于查找相关行的索引,则必须这么做。

* setup
  线程正开始进行一个ALTER TABLE操作。

* Sorting for group
  线程正在执行一个由GROUP BY指定的排序。

* Sorting for order
  线程正在执行一个由ORDER BY指定的排序。

* Sorting index
  线程正在对索引页进行排序,为了对MyISAM表进行操作时获得更优的性能。

* Sorting result
 对于一个SELECT语句,这与创建排序索引相似,但是是对非临时表。
诊断优化建议:可创建适当的索引。

* statistics
  服务器计算统计去规划一个查询。如果一个线程长时间处于这个状态,这个服务器的磁盘可能在执行其他工作。

* System lock
 这个线程正在请求或者等待一个内部的或外部的系统表锁。如果这个状态是由于外部锁的请求产生的,并且你没有使用多个正在访问相同的表的mysql服务器
 诊断优化建议:建议监控mysql锁信息
 
* Waiting for table level lock
  系统锁定后的下一个线程状态。线程已获得外部锁并且将请求内部表锁。

* Updating
  线程寻找更新匹配的行并进行更新。

* updating main table
  线程正在执行多表更新的第一部分,只从第一个表中更新。并且保存列和偏移量用来从其他(参考)表更新。

* updating reference tables
  线程正在执行多表更新的第二部分,并从其他表中更新匹配的行。

* User lock
  线程正在请求或等待一个GET_LOCK()调用所要求的咨询锁。对于SHOW PROFILE,这个状态意味这线程正在请求锁。(而非等待)

* User sleep
  线程调用了一个SLEEP()* Waiting for commit lock
  一个显式或隐式语句在提交时等待释放读锁

* Waiting for global read lock
  等待全局读锁。

* Waiting for release of readlock
  等待释放读锁。

* Waiting for tables, Waiting for table, Waiting for table flush
  线程获得一个通知,底层表结构已经发生变化,它需要重新打开表来获取新的结构。然而,重新打开表,它必须等到所有其他线程关闭这个有问题的表。
  这个通知产生通常因为另一个线程对问题表执行了FLUSH TABLES或者以下语句之一:
  FLUSH TABLES tbl_name, ALTER TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE, or OPTIMIZE TABLE.

* Waiting for lock_type lock
  等待各个种类的表锁。

* Waiting on cond
 一个普通的状态,线程正在等待一个条件为真。没有特定的状态信息可用。

* Writing to net
  服务器正在写一个网络数据包。

从图上我们可知,执行的sql在上下文切换频繁和次分布错误次数多。

分析MyISAM数据表

初始化数据
CREATE TABLE student_myisam AS
SELECT * FROM student


调整student_myisam数据表的存储引擎为MyISAM存储引擎

ALTER TABLE student_myisam ENGINE = MyISAM;
性能分析

查询学生条数

select count(*)
from student_myisam

使用SHOW PROFILES语句查看SQL语句信息。

SHOW PROFILES

查询学生条数的Query ID为31,查看SQL语句执行过程中所在线程的具体信息:

SHOW PROFILE FOR QUERY 31


MyISAM存储引擎由于缓存了数据表的元数据信息,当查询数据表中的记录条数时,并不需要统计数据表中的记录条数,而是直接从缓存的元数据中获取即可,不需要消耗太多的资源。

三、扩展

SQL执行过程中可能导致时间慢的原因


1. Sending data (最重要的一个过程★★★★★)
  线程正在读取和处理一条SELECT语句的行,并且将数据发送至客户端。由于在此期间会执行大量
  的磁盘访问(读操作),这个状态在一个指定查询的生命周期中经常是耗时最长的。
  
  对于一个普通查询来说,这个参数过大可分为两种情况
  1. 第一种是SQL本身,比如没有建立正确的索引,索引失效等等情况,这种数据体现在CPU_user 和CPU_sysyem字段 时间过长;
  2. 第二种是相应数据量过大,导致CPU调度时上下文频繁切换。这种数据体现在CONTEXT_INVOLUNTARY和CONTEXT_VOLUNTARY字段 时间过长;
     像:外网使用Navicat连接到远程数据库中。查询一个普通的SQL,在本地MySQL执行速度很快,但是使用远程服务器的MySQL就异常的缓慢。
  这时若查询profile详情,就会发现大量相应数据传输IO导致频繁的上下文切换消耗了大量的时间。

诊断优化建议:建议添加索引或查询时加上“limit”关键字,以减少从磁盘读取时返回的数据量。 

2. converting HEAP to MyISAM
   原译指的是:线程将一个内部临时表转换为磁盘上的MyISAM表。
   我们实际操作中可能出现的问题就是查询结果太大了导致内存不够,往磁盘上搬。
诊断优化建议:可以调整参数tmp_table_size和max_heap_table_size的值。

3.Creating tmp table
  创建了临时表
诊断优化建议:判断临时表产生的原因。

4.Coping to tmp table on disk
  把内存中临时表复制到磁盘
诊断优化建议:可以调整参数tmp_table_size和max_heap_table_size的值。


5.locked
  加锁
诊断优化建议:监控mysql锁的信息。
------------------------------------------------ 



四、总结

我们了解了show profie是什么,如何使用它。并分析了SQL执行过程中可能导致时间慢的原因。最后简单总结下几个命令,方便需要用的时候快速回忆。

-- 打开当前Session的profiling
set profiling = 1;

-- 查询最近一个查询的profiling信息
show profile;

-- 查询所有的profile信息
show profiles;

-- 查询某个Id的profile信息
show profile for query 5;

-- 查询某个Id的profile指定信息
show profile all for query 5;

四、参考

MySQL进阶技能树>查询优化 >SHOW PROFILE
MySQL中show profile详解
Show Profile官方文档地址

我是虚竹哥,我们明天见~

以上是关于第18天SQL进阶-查询优化- SHOW PROFILE(SQL 小虚竹)的主要内容,如果未能解决你的问题,请参考以下文章

第18天SQL进阶-查询优化- SHOW PROFILE(SQL 小虚竹)

第17天SQL进阶-查询优化- SHOW STATUS(SQL 小虚竹)

第17天SQL进阶-查询优化- SHOW STATUS(SQL 小虚竹)

第17天SQL进阶-查询优化- SHOW STATUS(SQL 小虚竹)

第15天SQL进阶-查询优化-慢查询日志(SQL 小虚竹)

第16天SQL进阶-查询优化一定要学EXPALIN (SQL 小虚竹)