mysql中explain分析sql执行type字段值的含义

# mysql5.7中type的类型达到了14种之多,这里只记录和理解最重要且经常遇见的六种类型,它们分别是all、index、range、ref、eq_ref、const。
# 从左到右,它们的效率依次是增强的。撇开sql的具体应用环境以及其他因素,应当尽量优化你的sql语句,使它的type尽量靠右
# 资料来自:https://blog.csdn.net/dennis211/article/details/78170079

创建2张表测试

CREATE TABLE `user` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `name` varchar(100) DEFAULT NULL,
   `version` int(10) DEFAULT NULL,
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

insert into `user` (`id`, `name`, `version`) values('1','aaaaaaa','11');
insert into `user` (`id`, `name`, `version`) values('2','bbbbbb','10');

CREATE TABLE `info` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `name` varchar(100) DEFAULT NULL,
   `age` int(10) DEFAULT NULL,
   `num` int(10) DEFAULT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `index_num` (`num`),
   KEY `index_name` (`name`)
 ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
 
insert into `info` (`id`, `name`, `age`, `num`) values('1','user1','1','11');
insert into `info` (`id`, `name`, `age`, `num`) values('2','user2','2','12');
insert into `info` (`id`, `name`, `age`, `num`) values('3','user3','3','13');
insert into `info` (`id`, `name`, `age`, `num`) values('4','user4','4','14');
insert into `info` (`id`, `name`, `age`, `num`) values('5','user5','5','15');

测试:

# type = all ,age字段无索引,则进行全表扫描
mysql -uroot -p"root" -e 'use aaa;EXPLAIN SELECT * FROM info WHERE age = 1;'
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | info  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    5 |    20.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

# type = index 且 Extra = NULL 时,为走索引的全表扫描。这样的索引,在排序时比type = all的排序速度快。
mysql -uroot -p"root" -e 'use aaa;EXPLAIN SELECT * FROM info ORDER BY id DESC;'
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
|  1 | SIMPLE      | info  | NULL       | index | NULL          | PRIMARY | 4       | NULL |    5 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+

# type = index 且 Extra = Using index 则为索引覆盖,从索引直接获取了数据,不用查表。name字段是普通索引
mysql -uroot -p"root" -e 'use aaa;EXPLAIN SELECT `name` FROM info;'
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key        | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | info  | NULL       | index | NULL          | index_name | 303     | NULL |    5 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+

# type = range , id自增主键,范围扫描
mysql -uroot -p"root" -e 'use aaa;EXPLAIN SELECT * FROM info WHERE id <= 100;'
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | info  | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    5 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+

# type = ref , name字段普通索引,sql使用了索引,但该索引列的值并不唯一,有重复
mysql -uroot -p"root" -e 'use aaa;EXPLAIN SELECT * FROM info WHERE `name` = "user1";'
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | info  | NULL       | ref  | index_name    | index_name | 303     | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------+

# type = eq_ref , 字段索引类型唯一,但单表测试结果是const,下面换联合查询可以。
mysql -uroot -p"root" -e 'use aaa;EXPLAIN SELECT * FROM info WHERE num = 11;'
+----+-------------+-------+------------+-------+---------------+-----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key       | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-----------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | info  | NULL       | const | index_num     | index_num | 5       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+-----------+---------+-------+------+----------+-------+

mysql -uroot -p"root" -e 'use aaa;EXPLAIN 
SELECT i.* 
FROM `user` AS u
LEFT JOIN info AS i 
ON u.id = i.id;'
+----+-------------+-------+------------+--------+---------------+---------+---------+----------+------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref      | rows | filtered | Extra       |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------+------+----------+-------------+
|  1 | SIMPLE      | u     | NULL       | index  | NULL          | PRIMARY | 4       | NULL     |    2 |   100.00 | Using index |
|  1 | SIMPLE      | i     | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | aaa.u.id |    1 |   100.00 | NULL        |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------+------+----------+-------------+

# const , id自增主键,精确定位
mysql -uroot -p"root" -e 'use aaa;EXPLAIN SELECT * FROM info WHERE id = 1;'
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | info  | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+

mysqldump: Error 2013: Lost connection to MySQL server during query when dumping table `xxx` at row: xxx

mysqldump备份时提示错误 Lost connection to MySQL server during query

# 查看超时参数

mysql> show global variables like '%timeout%';
可以看到下面2个参数的默认值
net_read_timeout 默认值 30
net_write_timeout 默认值 60

这2个参数的含义可以从  https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_net_read_timeout  查看。

# 解决方法,增大这2个参数的值:

mysql> set global net_read_timeout = 300; 
Query OK, 0 rows affected (0.03 sec)

mysql> set global net_write_timeout = 600;
Query OK, 0 rows affected (0.00 sec)

再次备份不报错了。

mysql临时开启慢日志记录

在不方便重启mysql情况下可以通过设置全局变量实现开启:

# 查询当前状态

SHOW VARIABLES LIKE '%slow_query%';
SHOW VARIABLES LIKE '%long_query_time%';

# 设置开启,设置sql执行时间超过3秒则记录

SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 3;
SET GLOBAL slow_query_log_file = "/usr/local/mysql/data/mysql-slow.log";

# 注意 slow_query_log_file 指定的目录要存在,并且mysql要有权限写入,否则会报错如下:
Variable ‘slow_query_log_file’ can’t be set to the value of ‘/usr/local/mysql/mysql-slow.log’

PS:
需要断开mysql客户端再次连接,执行SHOW VARIABLES LIKE ‘%long_query_time%’;才能看到修改后的时间。否则还是之前的时间。

mysql中其它的日志:

# 普通日志

SHOW VARIABLES LIKE '%general_log%'; # general_log、general_log_file
SET GLOBAL general_log='OFF';

# 错误日志

SHOW VARIABLES LIKE '%log_error%';   # log_error
SET GLOBAL log_error='/usr/local/mysql/data/mysql.err';

# 二进制日志

SHOW VARIABLES WHERE `variable_name` LIKE '%log_bin%' OR `variable_name` LIKE '%binlog%';

使用SQLAdvisor对SQL语句分析索引优化建议

SQLAdvisor是美团做的一个分析sql给出索引优化建议的工具。

建议放到一个独立的系统或者docker中。

系统:centos7
项目地址:https://github.com/Meituan-Dianping/SQLAdvisor

安装步骤:
# 拉取代码

[root@localhost ~]# git clone https://github.com/Meituan-Dianping/SQLAdvisor.git

# 安装percona yum源

[root@localhost ~]# wget -c https://www.percona.com/redir/downloads/percona-release/redhat/latest/percona-release-0.1-4.noarch.rpm
[root@localhost ~]# rpm -ivh percona-release-0.1-4.noarch.rpm

# 安装依赖

[root@localhost ~]# yum install -y Percona-Server-shared-56 cmake libaio-devel libffi-devel glib2 glib2-devel openssl-devel

# 编译依赖项sqlparser

[root@localhost ~]# cd /root/SQLAdvisor
[root@localhost SQLAdvisor]# cmake -DBUILD_CONFIG=mysql_release -DCMAKE_BUILD_TYPE=debug -DCMAKE_INSTALL_PREFIX=/usr/local/sqlparser ./
[root@localhost SQLAdvisor]# make && make install

# 安装SQLAdvisor

[root@localhost ~]# cd /root/SQLAdvisor/sqladvisor/
[root@localhost sqladvisor]# cmake -DCMAKE_BUILD_TYPE=debug ./
[root@localhost sqladvisor]# make
然后在当前路径下生成一个 sqladvisor 的可执行文件。

# 拷贝到/usr/local/bin/下便于命令调用

[root@localhost sqladvisor]# cp sqladvisor /usr/local/bin/

# 查看sqladvisor支持的参数

[root@localhost ~]# sqladvisor --help
-f, --defaults-file sqls file
-u, --username username
-p, --password password
-P, --port port
-h, --host host
-d, --dbname database name
-q, --sqls sqls
-v, --verbose 1:output logs 0:output nothing

# 命令行传参调用

[root@localhost ~]# sqladvisor -h x -P x -u x -p 'x' -d x -q "sql" -v 1
注意命令行传参时,参数名与值需要用空格隔开

# 使用配置文件传参调用

[root@localhost ~]# cat sql.cnf
[sqladvisor]
username=root
password=root
host=192.168.10.24
port=3306
dbname=test
sqls=SELECT * FROM sys_login_log WHERE userId = 100580;

如果多个sql语句格式即 sql=sql1;sql2;sql3…
# 开始分析

[root@localhost ~]# sqladvisor -f sql.cnf -v 1
第1步: 对SQL解析优化之后得到的SQL:select `*` AS `*` from `test`.`sys_login_log` where (`userId` = 100580)
第2步:开始解析where中的条件:(`userId` = 100580)
show index from sys_login_log
show table status like 'sys_login_log'
select count(*) from ( select `userId` from `sys_login_log` FORCE INDEX( PRIMARY ) order by id DESC limit 10000) `sys_login_log` where (`userId` = 100580)
第3步:表sys_login_log的行数:365166,limit行数:10000,得到where条件中(`userId` = 100580)的选择度:10000
第4步:开始验证 字段userId是不是主键。表名:sys_login_log
show index from sys_login_log where Key_name = 'PRIMARY' and Column_name ='userId' and Seq_in_index = 1
第5步:字段userId不是主键。表名:sys_login_log
第6步:开始验证 字段userId是不是主键。表名:sys_login_log
show index from sys_login_log where Key_name = 'PRIMARY' and Column_name ='userId' and Seq_in_index = 1
第7步:字段userId不是主键。表名:sys_login_log
第8步:开始验证表中是否已存在相关索引。表名:sys_login_log, 字段名:userId, 在索引中的位置:1
show index from sys_login_log where Column_name ='userId' and Seq_in_index =1
第9步:开始输出表sys_login_log索引优化建议:
Create_Index_SQL:alter table sys_login_log add index idx_userId(userId)
第10步: SQLAdvisor结束!

提示 创建索引 Create_Index_SQL:alter table sys_login_log add index idx_userId(userId)


 

mysql开启查询缓存

文章内容不完善,部分内容讲解不到位,仅供参考:

mysql> show variables like ‘%cache%’;
+——————————+————+
| Variable_name                | Value      |
+——————————+————+
| binlog_cache_size            | 32768      |
| have_query_cache             | YES        |
| key_cache_age_threshold      | 300        |
| key_cache_block_size         | 1024       |
| key_cache_division_limit     | 100        |
| max_binlog_cache_size        | 4294963200 |
| query_cache_limit            | 1048576    |
| query_cache_min_res_unit     | 4096       |
| query_cache_size             | 0          |
| query_cache_type             | ON         |
| query_cache_wlock_invalidate | OFF        |
| table_definition_cache       | 256        |
| table_open_cache             | 64         |
| thread_cache_size            | 0          |
+——————————+————+
14 rows in set (0.00 sec)

have_query_cache 部分为yes

利用mysql数据库中的user表做测试:

mysql> select * from user\G;

查看是否缓存:
mysql> show status like ‘%Qcache%’;
+————————-+——-+
| Variable_name           | Value |
+————————-+——-+
| Qcache_free_blocks      | 0     |
| Qcache_free_memory      | 0     |
| Qcache_hits             | 0     |
| Qcache_inserts          | 0     |
| Qcache_lowmem_prunes    | 0     |
| Qcache_not_cached       | 0     |
| Qcache_queries_in_cache | 0     |
| Qcache_total_blocks     | 0     |
+————————-+——-+
8 rows in set (0.00 sec)

看到所有项目为0,表示缓存功能并没有运行起来。

修改my.cnf配置文件,添加:

query_cache_type = 1     
#0 -– 不启用查询缓存;
 1 -– 启用查询缓存,只要符合查询缓存的要求,客户端的查询语句和记录集斗可以缓存起来,共其他客户端使用;
 2 -– 启用查询缓存,只要查询语句中添加了参数:SQL_CACHE,且符合查询缓存的要求,客户端的查询语句和记录集,则可以缓存起来,共其他客户端使用;
query_cache_limit = 1M    //限制查询缓存大小
query_cache_size = 32M    //设置缓存占用内存大小

重启mysql,测试。
mysql> select * from user\G;
mysql> show status like ‘%Qcache%’;
+————————-+———-+
| Variable_name           | Value    |
+————————-+———-+
| Qcache_free_blocks      | 1        |
| Qcache_free_memory      | 33545600 |
| Qcache_hits             | 0        |
| Qcache_inserts          | 0        |
| Qcache_lowmem_prunes    | 0        |
| Qcache_not_cached       | 6        |
| Qcache_queries_in_cache | 0        |
| Qcache_total_blocks     | 1        |
+————————-+———-+
8 rows in set (0.00 sec)

Query cache 作用于整个 MySQL Instance,主要用来缓存 MySQL 中的 ResultSet,也就是一条SQL语句执行的结果集,所以仅仅只能针对select语句。当我们打开了 Query Cache 功能,MySQL在接受到一条select语句的请求后,如果该语句满足Query Cache的要求(未显式说明不允许使用Query Cache,或者已经显式申明需要使用Query Cache),MySQL 会直接根据预先设定好的HASH算法将接受到的select语句以字符串方式进行hash,然后到Query Cache 中直接查找是否已经缓存。也就是说,如果已经在缓存中,该select请求就会直接将数据返回,从而省略了后面所有的步骤(如 SQL语句的解析,优化器优化以及向存储引擎请求数据等),极大的提高性能。

当然,Query Cache 也有一个致命的缺陷,那就是当某个表的数据有任何任何变化,都会导致所有引用了该表的select语句在Query Cache 中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache 可能会得不偿失。

Query Cache的使用需要多个参数配合,其中最为关键的是 query_cache_size 和 query_cache_type ,前者设置用于缓存 ResultSet 的内存大小,后者设置在何场景下使用 Query Cache。在以往的经验来看,如果不是用来缓存基本不变的数据的MySQL数据库,query_cache_size 一般 256MB 是一个比较合适的大小。当然,这可以通过计算Query Cache的命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))来进行调整。 query_cache_type可以设置为0(OFF),1(ON)或者2(DEMOND),分别表示完全不使用query cache,除显式要求不使用query cache(使用sql_no_cache)之外的所有的select都使用query cache,只有显示要求才使用query cache(使用sql_cache)。