Mysql高级篇学习总结11:定位执行慢的sql方法、分析查询语句EXPLAIN的使用

1、定位执行慢的sql

mysql的慢查询日志,用来记录在mysql中响应时间超过阈值的语句,具体指运行时间超过long_query_time值的sql,会被记录到慢查询中。

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

1.1 开启慢查询日志参数

1、开启slow_query_log
可以使用以下命令查看是否开启慢查询日志:

mysql> show variables like '%slow_query_log';
+----------------+-------+
| Variable_name  | Value |
+----------------+-------+
| slow_query_log | OFF   |
+----------------+-------+
1 row in set (0.63 sec)

可以开启该参数:

mysql> set global slow_query_log = on;
Query OK, 0 rows affected (0.59 sec)

开启该参数之后,再来查看该参数相关的变量:
可以看到,此时slow_query_log已经打开了,同时该文件对应磁盘上的位置也出来了。

mysql> show variables like '%slow_query_log%';
+---------------------+-----------------------------------+
| Variable_name       | Value                             |
+---------------------+-----------------------------------+
| slow_query_log      | ON                                |
| slow_query_log_file | /var/lib/mysql/koping-HP-slow.log |
+---------------------+-----------------------------------+
2 rows in set (0.00 sec)

2、修改long_query_time阈值
接下来先查看慢查询的时间阈值设置:

mysql> show variables like '%long_query_time%';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set (0.00 sec)

默认是10秒以上的才是慢查询,这里为了演示方便,将时间缩短为1秒:
全局修改:set global long_query_time=1;
会话修改:set long_query_time=1;

-- 全局修改
mysql> set global long_query_time=1;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like '%long_query_time%';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set (0.00 sec)

-- 会话修改
mysql> set long_query_time=1;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like '%long_query_time%';
+-----------------+----------+
| Variable_name   | Value    |
+-----------------+----------+
| long_query_time | 1.000000 |
+-----------------+----------+
1 row in set (0.01 sec)

1.2 查看慢查询数目

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

mysql> SHOW GLOBAL STATUS LIKE '%Slow_queries%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Slow_queries  | 0     |
+---------------+-------+
1 row in set (0.00 sec)

1.3 案例演示

这里借助上一篇博文(Mysql高级篇学习总结10:适合创建索引的11种情况、不适合创建索引7的种情况)在1.1中创建的student_info表来进行演示,该表中有100万条数据。

1.4 测试及分析

运行一条耗时较长的sql语句:

mysql> SELECT student_id, COUNT(*) AS num FROM student_info   GROUP BY student_id ORDER BY create_time DESC LIMIT 100;
100 rows in set (1.92 sec)

可以看到该sql的语句超过了1秒,此时再来查询慢查询个数,此时变为了1了。

mysql> SHOW GLOBAL STATUS LIKE '%Slow_queries%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Slow_queries  | 1     |
+---------------+-------+
1 row in set (0.00 sec)

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

发下有了慢查询之后,可以通过慢查询日志分析工具进行分析了。首先看下改工具有哪些参数:

koping@koping-HP:~$ mysqldumpslow -h
Option h requires an argument
ERROR: bad option

Usage: mysqldumpslow [ OPTS... ] [ LOGS... ]

Parse and summarize the MySQL slow query log. Options are

  --verbose    verbose
  --debug      debug
  --help       write this text to standard output

  -v           verbose
  -d           debug
  -s ORDER     what to sort by (al, at, ar, c, l, r, t), 'at' is default
                al: average lock time
                ar: average rows sent
                at: average query time
                 c: count
                 l: lock time
                 r: rows sent
                 t: query time  
  -r           reverse the sort order (largest last instead of first)
  -t NUM       just show the top n queries
  -a           don't abstract all numbers to N and strings to 'S'
  -n NUM       abstract numbers with at least n digits within names
  -g PATTERN   grep: only consider stmts that include this string
  -h HOSTNAME  hostname of db server for *-slow.log filename (can be wildcard),
               default is '*', i.e. match all
  -i NAME      name of server instance (if using mysql.server startup script)
  -l           don't subtract lock time from total time

接着来查询下目前慢查询日志记录的前5条慢查询语句有哪些。可以看到,我们确实看到了刚刚测试中用到的那条慢查询语句。

mysqldumpslow -a -s t -t 5 /var/lib/mysql/koping-HP-slow.log
root@koping-HP:~# mysqldumpslow -a -s t -t 5 /var/lib/mysql/koping-HP-slow.log

Reading mysql slow query log from /var/lib/mysql/koping-HP-slow.log
Count: 1  Time=1.93s (1s)  Lock=0.00s (0s)  Rows=100.0 (100), root[root]@localhost
  SELECT student_id, COUNT(*) AS num FROM student_info   GROUP BY student_id ORDER BY create_time DESC LIMIT 100

Died at /usr/bin/mysqldumpslow line 167, <> chunk 1.

1.6 关闭慢查询日志

1)停止慢查询日志功能:

set global slow_query_log=off;

2、分析查询语句:EXPLAIN

2.1 概述

定位到了查询慢的sql之后,就可以使用EXPALIN或者DESCRIBE工具做针对性的分析查询语句。

2.2 基本语法

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

EXPALIN SELECT select_options
或者
DESCRIBE SELECT select_options

注意:EXPALIN输出的是执行计划,并不是实际真实执行了该语句。

EXPALIN语句输出的各个列的作用如下:
在这里插入图片描述

2.3 测试数据准备

1)创建2张表

CREATE TABLE s1 (
    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;


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)创建存储函数

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 ;

3)创建2个存储过程

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 ;


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 ;

4)#调用存储过程,往两张表里分别插入1万条数据

mysql> CALL insert_s1(10001,10000);
Query OK, 0 rows affected (9.64 sec)

mysql> CALL insert_s2(10001,10000);
Query OK, 0 rows affected (11.64 sec)

5)查看测试用的数据是否查看成功

mysql> SELECT COUNT(*) FROM s1;
+----------+
| COUNT(*) |
+----------+
|    10000 |
+----------+
1 row in set (0.00 sec)

mysql> SELECT COUNT(*) FROM s2;
+----------+
| COUNT(*) |
+----------+
|    10000 |
+----------+
1 row in set (0.00 sec)

2.4 EXPLAIN各列作用

2.4.1 table

查询的每一行记录都对应者一个单表

mysql> EXPLAIN SELECT * FROM s1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9895 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                 |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9895 |   100.00 | NULL                                  |
|  1 | SIMPLE      | s2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9895 |   100.00 | Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
2 rows in set, 1 warning (0.00 sec)

2.4.2 id

在一个大的查询语句中,每个SELECT关键字都对应着一个唯一的id

比如下面有2个select查询语句,那么就有2个id。

mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3='a';
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
|  1 | PRIMARY     | s1    | NULL       | ALL   | idx_key3      | NULL     | NULL    | NULL | 9895 |   100.00 | Using where |
|  2 | SUBQUERY    | s2    | NULL       | index | idx_key1      | idx_key1 | 303     | NULL | 9895 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

但是,需要注意,查询优化器可能对涉及子查询的查询语句进行重写

比如下面的这条语句,虽然有2个select,但是查询优化器进行了优化,因此结果还是只有1个id:

mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key2 FROM s2 WHERE common_field='a');
+----+-------------+-------+------------+------+---------------+----------+---------+-----------------+------+----------+------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key      | key_len | ref             | rows | filtered | Extra                              |
+----+-------------+-------+------------+------+---------------+----------+---------+-----------------+------+----------+------------------------------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | idx_key1      | NULL     | NULL    | NULL            | 9895 |   100.00 | Using where                        |
|  1 | SIMPLE      | s2    | NULL       | ref  | idx_key2      | idx_key2 | 5       | dbtest1.s1.key1 |    1 |    10.00 | Using index condition; Using where |
+----+-------------+-------+------------+------+---------------+----------+---------+-----------------+------+----------+------------------------------------+
2 rows in set, 2 warnings (0.00 sec)

小结id:

  • id如果相同,可以认为是一组,从上往下顺序执行
  • 在所有组中,id值越大,优先级越高,越先执行
  • 关注点:id号每个号码,表示一趟独立的查询,一个sql的查询趟数越少越好

2.4.3 select_type

mysql为每一个select关键字代表的小查询都定义了一个称之为select_type的属性,意思是我们只要知道了某个小查询的select_type属性,就知道了这个小查询在整个大查询中扮演了一个什么角色。我们看一下select_type都能取哪些值:
在这里插入图片描述
在这里插入图片描述

2.4.4 partitions

代表分区表中的命中情况,非分区表,该项为NULL。一般情况下的查询语句的执行计划的partitions列的值都是NULL

2.4.5 type

执行计划的一条记录就代表着mysql对某个表的执行查询时的访问方法,其中type列就表明了这个访问方法是什么。

结果值从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > All

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

2.4.6 possible_keys和key

possible_keys列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些。

key列表示实际用到的索引有哪些,如果为NULL,则没有使用索引。

2.4.7 key_len

实际用到的索引长度(即字节数)

2.4.8 ref

当使用索引列等值查询时,与索引列进行等值匹配的对象信息。

该字段与type字段是对应匹配一起看的。

2.4.9 rows

预估的需要读取的记录树,该值越小越好。

2.4.10 filtered

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

对于单表查询来说,该列没有什么意义。在连接查询中驱动表对应的执行计划记录的filtered值,它决定了被驱动动表要执行的次数:rows * filtered

2.4.11 Extra

Extra是用来说明一些额外信息的,可以提供mysql到底是如何执行给定的查询语句的。

猜你喜欢

转载自blog.csdn.net/xueping_wu/article/details/125708136