MySQL优化总结之分析SQL执行过程(explain、show profile、trace)

一、定位慢查询

在实际生产环境中,可能因为开发写了不正确的SQL语句,索引优化的不好,或其他查询操作而导致数据库整体性能下降。我们可以开启mysq慢查询,一旦有sql执行时间超过了设置的慢查询时间,就会被记录到慢查询日志中。这样我们就可以从慢查询日志中定位慢查询sql,然后分析慢查询日志就会知道问题出在哪了。

1.使用show status查询数据库的运行状况

//显示数据库运行状态
SHOW STATUS
//显示数据库运行总时间
SHOW STATUS LIKE 'uptime'
//显示连接的次数
SHOW STATUS LIKE 'connections'
//显示执行CRUD的次数
SHOW STATUS LIKE 'com_select'
SHOW STATUS LIKE 'com_insert'
SHOW STATUS LIKE 'com_update'
SHOW STATUS LIKE 'com_delete'

2.定位慢查询SQL

查看慢查询相关信息

//显示慢查询次数
SHOW STATUS LIKE 'slow_queries'
//显示慢查询时间,默认为10s
SHOW VARIABLES LIKE 'long_query_time'

mysql的慢查询默认是关闭的,慢查询日志开启后对Mysql的整体性能或多或少都会造成负面影响,如果没有调优的需求,不建议开启慢查询日志记录。

可以通过操作终端开启session级别慢查询,也可以在配置文件中修改永久生效。

【session级别】
#开启慢查询
SET slow_query_log='ON';
#设置慢查询日志存放位置
SET slow_query_log_file='/usr/local/mysql/data/slow.log';
#设置慢查询时间
SET long_query_time=10

【global级别】
SET global slow_query_log='ON';
SET global slow_query_log_file='/usr/local/mysql/data/slow.log';
SET global long_query_time=3

如果是修改配置文件,修改后需要重启mysql才会生效。Linux下修改my.cnf,Windows下修改my.ini。

#开启慢查询
slow-query-log=1
#慢查询的文件路径
slow_query_log_file="D:/Program Files/MySQL/Log/mysql-slow.log"
#慢查询时间。默认为10秒
long_query_time=10

3.分析慢查询

//查看是否启用慢日志记录和状态
show variables like "%slow%"

如果慢查询日志中内容较多,可以使用Mysql自带的慢查询日志分析工具mysqldumpslow来对慢查询日志进行分类汇总。mysqldumpslow会自动将文本完全一致但变量不同的SQL语句视为同一个语句进行统计,变量值用N来代替。

mysqldumpslow -s r -t 10 /data/dbdata/frem-slow.log

二、使用explain查看SQL执行计划

官方文档:Optimizing Queries with EXPLAIN

mysql会将慢查询记录到慢查询日志中,这时我们就可以针对这些慢查询的sql进行分析和优化,需要用到explain命令。

explain [要分析的sql]

分析结果中有如下几列:

+----+-------------+---------+------+---------------+------+---------+------+------+-------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+------+-------+-------+

下面介绍各列的含义。可以参考官方文档说明:Explain Output Colums

id

表示select查询序列号。id值越大,越优先执行。如果id相同,执行顺序由上至下;

       

select_type

表示查询操作的类型。主要用于区分普通查询、子查询、联合查询等几种查询情况。有这些取值:simple,primary,subquery,derived,union,union result

①simple:表示简单查询,只有一个select操作,即不使用连接和union。

#只有一个select操作,所以都是简单查询
select id from emp;
select id from emp join dept on emp.dept_id=dept.id;

②primary:表示主查询。子查询语句中的最外层select,或union操作的第一个select。

#子查询形式:第一个select操作为primary
select * from app_school where id = (select id from app_school where id=100);

#union形式:第一个select操作为primary
select * from app_school where id=100
union
select * from app_school where id=101;

③subquery:表示子查询。子查询语句中的内层select。

#第二个select操作为subquery
select * from app_school where id = (select id from app_school where id=100);

④derived:表示FROM后跟着的select查询,会被标记为derived(导出表/衍生表)。

#第二个select操作为derived
select * from (select id from app_school) t;

⑤union:表示UNION操作后面的select查询。

#第二个select操作为union
select * from app_school where id=100
union
select * from app_school where id=101;

⑥union result:表示获取UNION最后结果的查询。

#第一个select操作为primary
#第二个select操作为union
#获取最终结果的操作为union result
select * from app_school where id=100
union
select * from app_school where id=101;

table

表示查询用到的表。

type

表示找到匹配行用到的访问类型。最为常见的类型有system,const,eq_ref,ref,range,index,All按照性能从高到低顺序如下:NULL-->system-->const-->eq-ref-->ref-->range-->index-->All 。一般来说,要让查询至少达到range级别,最好能达到ref级别。

NULL:不用访问表或索引,就可直接得出结果。

system:该表仅有一行(等同于系统表),这是const类型的一个特例。系统表中的数据通常已经加载到了内存中,所以不需要磁盘IO。
例子1:查询系统表

例子2:内层嵌套(const)返回了一个临时表,外层嵌套从临时表中查询,其扫描类型也是system,也不需要磁盘IO。

const:最多只有一个匹配行,在查询开始时读取。因为只有一行,所以该行中列的值可以被优化器当作常量来处理。const表非常快,因为只会读取一次。

当使用primary key或unique index进行查询时就会用到const简单地说const就是直接按主键或唯一键取值。

例如,上例的第二个例子,payment表的访问类型就是const,其通过主键来取值。此时payment表可以当做const表使用。

eq_ref:使用唯一索引,对于每个索引键值,表中只有一条记录匹配。简单说,就是多表连接中使用primary索引或unique索引(NOT NULL)作为关联条件。这是除了system和const外最好的连接类型。

eq_ref被用于使用=操作符比较的索引列,比较值可以是常量或表达式,该表达式会使用表中的列,该列会在此表之前读取。    

注意const和eq_ref的区别:简单地说const是直接按主键或唯一键读取,eq_ref用于联表查询的情况,按联表的主键或唯一键联合查询。

ref:使用非唯一索引,或唯一索引的前缀扫描,返回匹配某个单独值的所有行(可能匹配多个行)。ref 可用于使用 = 或 <=> 运算符比较的索引列。

ref还经常出现在join操作中

ref_or_null:与ref类似,区别在MySQL会额外搜索含有NULL值的行。这种连接类型优化在解析子查询时最常使用。

SELECT * FROM t WHERE key_column=expr OR key_column IS NULL;

index_merge:索引合并优化。explain输出中key列显示使用的索引列表,key_len列包含所用到的索引的最长关键元素列表。

unique_subquery:取代eq_ref,用于IN后面是一个查询主键字段的子查询。 unique_subquery只是一个索引查找函数,完全取代子查询,以提高工作效率。

value IN (SELECT primary_key FROM single_table WHERE some_expr)

index_subquery:与unique subquery类似,取代了IN子查询,用于IN的后面是查询非唯一索引字段的子查询。

value IN (SELECT key_column FROM single_table WHERE some_expr)

⑩range:只检索指定范围的行,使用索引来选择行。key列显示使用了哪个索引,key_len列包含所使用索引的最长关键元素。用于key列与常量的比较,可以是=<>>>=<<=IS NULL<=>BETWEENLIKE或者IN()操作。

SELECT * FROM tbl_name WHERE key_column = 10;

SELECT * FROM tbl_name WHERE key_column BETWEEN 10 and 20;

SELECT * FROM tbl_name WHERE key_column IN (10,20,30);

SELECT * FROM tbl_name WHERE key_part1 = 10 AND key_part2 IN (10,20,30);

实例:

11.index:索引树全扫描。遍历整个索引树来查询匹配的行。

12.ALL:全表扫描,性能最差。

possible_keys和key

possible_keys表示查询时可能使用到的索引,而key表示实际选择使用的索引

key_len

表示使用到的索引列的长度

ref

表示该表的索引字段关联了哪张表的哪个列

rows

表示预估扫描行的数量。注意是预估扫描的行数,并不是实际扫描的行数,因为这是执行计划,只是执行前的一个评估决策。

Extra

表示执行情况的说明和描述,包含不适合在其它列中显示但对执行计划非常重要的额外信息。可以参考:MySQL中explain执行计划中额外信息字段(Extra)详解

记录几个重要的。更多内容参考官方文档:EXPLAIN Extra Information

  • Using index:使用了覆盖索引,直接从索引中就能获取到结果。查询列表和查询条件只包含了某个索引中的列,直接通过索引就能获取到结果,不需要进行回表。
  • Using where:在查找使用索引的情况下,索引并不能覆盖到需要查询的所有列,需要回表去查询所需的数据。(where条件中除了索引包含的列外,还有索引未包含的列)
  • Using index condition:使用了索引条件下推(ICP)。MySQL的架构中分成了server层和引擎层。索引下推(ICP)其实就是实现了index filter技术,将原来的在server层的table filter中可以进行的过滤操作,移到引擎层面使用index filter进行处理,不再需要回表进行table filter。
  • Using filesort:使用了文件排序。当查询语句包含ORDER BY时,如果无法使用索引来完成排序,则需要进行额外的排序操作。
  • Using temporary:使用临时表来保存中间结果,用于完成排序、去重等操作。比如我们在执行许多包含 DISTINCT 、 GROUP BY 、ORDER BY、 UNION 等子句的查询过程中,如果不能有效利用索引来完成查询, 就需要建立内部的临时表来执行查询。
  • Using index for group-by:待补充!待补充!待补充!

三、使用trace分析优化器如何选择执行计划

官方文档:Tracing the Optimizertracing-example

Mysql5.6提供了对sql的跟踪,能够进一步了解为什么优化器选择A执行计划而不选择B执行计划,帮助我们更好地理解优化器的行为。下面是典型的使用方式:

# 开启trace(默认关闭),设置json格式
SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on;
//设置内存,避免解析过程中因为默认内存过小而不能够完整显示。
SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000;

# 执行sql
SELECT ...; # your query here

# 检查INFORMATION_SCHEMA.OPTIMIZER_TRACE查看跟踪信息
SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE \G;

# 关闭trace
SET optimizer_trace="enabled=off";

也可以将上面的跟踪信息输出到文件中,方便我们阅读。

SELECT TRACE INTO DUMPFILE <filename> FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;

可以直接参考官方给出了案例:tracing-example

四、对SQL性能分析

除了通过explain分析SQL的执行计划外,还可以对SQL进行性能分析。MySQL提供了show profiles和show profile命令,可以用来获取SQL执行过程中的资源使用和耗时情况,对于分析SQL的性能瓶颈有非常大的帮助。但是这个功能在MySQL5.7中已经被废弃了,取而代之的是使用Performance Schema(系统表信息)来提供同样的功能。不过令人诟病的是,performance schema表众多,这些表和 information_schema 中的部分表也缠夹不清,让大家用得很不习惯。

使用show profile(被Performance Schema取代)

官方文档:SHOW PROFILE StatementSHOW PROFILES Statement

show profile的使用步骤如下:

#0.查看是否支持profile
SELECT @@have_profiling
#1.开启session级别的profile(默认关闭)
set profiling=1;

#2.执行select语句
select ……
#3.查看该sql语句的Query ID show profiles
#4.查看执行中线程的每个状态和消耗的时间 show profile
for query [上面的Query ID]

 

Performance Schema

官方文档:Performance SchemaQuery Profiling Using Performance Schema

1.开启Performance Schema功能

该功能默认是开启的,对应的配置文件如下

[mysqld]
performance_schema=ON

2.配置

配置表setup_actors

默认情况下,performance_schema功能打开后,将会收集所有用户的SQL执行历史事件,因为收集的信息太多,对数据库整体性能有一定影响,而且也不利于排查指定SQL的性能问题,因此需要修改setup_actors表的配置,只收集特定用户的历史事件信息。setup_actors表配置如下:

 

SELECT * FROM performance_schema.setup_actors;
+------+------+------+---------+---------+
| HOST | USER | ROLE | ENABLED | HISTORY |
+------+------+------+---------+---------+
| %    | %    | %    | YES     | YES     |
+------+------+------+---------+---------+

UPDATE performance_schema.setup_actors
       SET ENABLED = 'NO', HISTORY = 'NO'
       WHERE HOST = '%' AND USER = '%';

INSERT INTO performance_schema.setup_actors
       (HOST,USER,ROLE,ENABLED,HISTORY)
       VALUES('localhost','test_user','%','YES','YES');
       SELECT * FROM performance_schema.setup_actors;
+-----------+-----------+------+---------+---------+
| HOST      | USER      | ROLE | ENABLED | HISTORY |
+-----------+-----------+------+---------+---------+
| %         | %         | %    | NO      | NO      |
| localhost | test_user | %    | YES     | YES     |
+-----------+-----------+------+---------+---------+

配置表setup_instruments

启用statement和stage监视器。

UPDATE performance_schema.setup_actors
       SET ENABLED = 'NO', HISTORY = 'NO'
       WHERE HOST = '%' AND USER = '%';

INSERT INTO performance_schema.setup_actors
       (HOST,USER,ROLE,ENABLED,HISTORY)
       VALUES('localhost','test_user','%','YES','YES');

配置表setup_consumers

启用events_statements_*,events_stages_* 开头的事件类型消费。

UPDATE performance_schema.setup_consumers
       SET ENABLED = 'YES'
       WHERE NAME LIKE '%events_statements_%';

UPDATE performance_schema.setup_consumers
       SET ENABLED = 'YES'
       WHERE NAME LIKE '%events_stages_%';    

3.收集具体SQL的性能分析

执行SQL语句

SELECT * FROM employees.employees WHERE emp_no = 10001;
+--------+------------+------------+-----------+--------+------------+
| emp_no | birth_date | first_name | last_name | gender | hire_date |
+--------+------------+------------+-----------+--------+------------+
|  10001 | 1953-09-02 | Georgi     | Facello   | M      | 1986-06-26 |
+--------+------------+------------+-----------+--------+------------+

获取SQL的事件ID

SELECT EVENT_ID, TRUNCATE(TIMER_WAIT/1000000000000,6) as Duration, SQL_TEXT 
    FROM performance_schema.events_statements_history_long WHERE SQL_TEXT like '%10001%';
+----------+----------+--------------------------------------------------------+
| event_id | duration | sql_text                                               |
+----------+----------+--------------------------------------------------------+
|       31 | 0.028310 | SELECT * FROM employees.employees WHERE emp_no = 10001 |
+----------+----------+--------------------------------------------------------+

获取各阶段执行耗时

SELECT event_name AS Stage, TRUNCATE(TIMER_WAIT/1000000000000,6) AS Duration
       FROM performance_schema.events_stages_history_long WHERE NESTING_EVENT_ID=31;
+--------------------------------+----------+
| Stage                          | Duration |
+--------------------------------+----------+
| stage/sql/starting             | 0.000080 |
| stage/sql/checking permissions | 0.000005 |
| stage/sql/Opening tables       | 0.027759 |
| stage/sql/init                 | 0.000052 |
| stage/sql/System lock          | 0.000009 |
| stage/sql/optimizing           | 0.000006 |
| stage/sql/statistics           | 0.000082 |
| stage/sql/preparing            | 0.000008 |
| stage/sql/executing            | 0.000000 |
| stage/sql/Sending data         | 0.000017 |
| stage/sql/end                  | 0.000001 |
| stage/sql/query end            | 0.000004 |
| stage/sql/closing tables       | 0.000006 |
| stage/sql/freeing items        | 0.000272 |
| stage/sql/cleaning up          | 0.000001 |
+--------------------------------+----------+

五、show processlist

官方文档:SHOW PROCESSLIST Statement

show processlist / show full processlist 也是一个常用命令,可以用来查看当前mysql 是否有压力,都在跑什么语句,当前语句耗时多久了,有没有什么慢 SQL 正在执行之类的。

可以看到总共有多少链接数,用户正在运行的线程,哪些线程有问题。其显示的信息都是来自系统库information_schema中的processlist 表。

show full processlist

需要注意,除了 root 用外,用户只能看到自己正在运行的线程,除非单独为这个用户赋予PROCESS 权限。

 

参考:show processlist 详解

 

posted @ 2019-07-08 16:00  静水楼台/Java部落阁  阅读(5316)  评论(0编辑  收藏  举报