一、查看系统性能参数
通过SHOW STATUS语句查询一些MySQL数据库服务器的性能参数、执行频率。
SHOW [GLOBAL|SESSION] STATUS LIKE '参数';
一些常用的性能参数如下:
- Connections:连接MySQL服务器的次数。
- Uptime:MySQL服务器的上线时间。单位:s
- 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:删除操作的次数。
- last_query_cost:上一次查询的效率,返回查询所加载的数据页数
二、定位执行慢的SQL:慢查询日志
1.开启slow_query_log
show VARIABLES like '%slow_query_log%'; #默认为0,未开启
set global slow_query_log=1;
slow_query_log_file参数表示慢查询日志的存放位置
2. 修改long_query_time阈值
接下来我们来看下慢查询的时间阈值设置,使用如下命令:
show variables like '%long_query_time%';
默认为10秒,即超过10秒的sql语句才会被记录到慢查询日志。
#设置global的方式对当前session的long_query_time无效。
set global long_query_time = 1;
set long_query_time=1;
3. 查看慢查询数目
SHOW GLOBAL STATUS LIKE '%Slow_queries%';
三、查看 SQL 执行成本:SHOW PROFILE
1.查看功能是否开启
show variables like 'profiling';
2.开启profiling功能
set profiling = 1;
3.查看当前会话都有哪些 profiles
show profiles;
4.查看具体的profile
show profile; #查看最近的sql语句
show profile cpu,block io for query 2; #根据query号精确查看
show profile的常用查询参数:
① ALL:显示所有的开销信息。 ② BLOCK IO:显示块IO开销。 ③ CONTEXT SWITCHES:上下文切换开销。 ④ CPU:显示CPU开销信息。 ⑤ IPC:显示发送和接收开销信息。 ⑥ MEMORY:显示内存开销信息。 ⑦ PAGE FAULTS:显示页面错误开销信息。 ⑧ SOURCE:显示和Source_function,Source_file, Source_line相关的开销信息。 ⑨ SWAPS:显示交换次数开销信息。
四、分析查询语句:EXPLAIN
定位了查询慢的SQL之后,我们就可以使用EXPLAIN工具做针对性的分析查询语句。通过该工具可以查看sql语句内部的执行计划。
#直接在要执行的语句前加上EXPLAIN
EXPLAIN SELECT select_options
如:
mysql> explain select * from student_info;
+----+-------------+--------------+------------+------+---------------+------+---------+------+--------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+------+---------------+------+---------+------+--------+----------+-------+
| 1 | SIMPLE | student_info | NULL | ALL | NULL | NULL | NULL | NULL | 918082 | 100.00 | NULL |
+----+-------------+--------------+------------+------+---------------+------+---------+------+--------+----------+-------+
1 row in set, 1 warning (0.00 sec)
EXPLAIN语句输出的各个列的作用如下:
列名 | 描述 |
---|---|
id | 一张表中select的查询序列号,id越大越先执行 |
select_type | SELECT关键字对应的查询类型 |
table | 表名 |
partitions | 匹配的分区信息 |
type | 针对单表的访问方法 |
possible_keys | 可能用到的索引 |
key | 实际用到的索引 |
key_len | 实际用到的索引长度,针对关联查询 |
ref | 当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows | 预估的需要读取的记录条数 |
filtered | 某个表经过搜索条件过滤后剩余记录条数的百分比 |
Extra | 一些额外的信息 |
EXPLAIN各列作用:
首先准备几张表:
#用户表
CREATE TABLE `person` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(255) NOT NULL,
`realname` varchar(20) DEFAULT NULL,
`age` int DEFAULT NULL,
`telphone` int DEFAULT NULL,
`birthday` date DEFAULT NULL,
`interesting` varchar(255) DEFAULT NULL,
`sex` varchar(1) DEFAULT NULL,
`headphoto` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
#朋友关系表
CREATE TABLE `friends` (
`id` int NOT NULL AUTO_INCREMENT,
`person_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`friend_id` varchar(255) NOT NULL,
`comments` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
1.table
table列代表表的表名(有时不是真实的表名字,可能是简称),有些也有可能有临时表。
示例:
#关联查询在底层会查询两张表
explain select *
from person p
inner join friends f
2.id
在一个大的查询语句中每个SELECT关键字都对应一个唯一的id
如上的联合查询中,只有一个select关键字,所以前面的id都是1,代表这两个查询优先级是一样的,关系是并列的,id相同的第一个表称为驱动表(person表),后面的称为被驱动表。
示例1:
#子查询
explain select *
from person
where age > (select age from person where id=2)
可以看到查询中有两个select,他们的id并不相同,不是并列的关系。
示例2:
#union求并集
explain select id from person union select id from friends;
用union求一个并集他们的id也各不相同,额外的,union还会生成一个临时表,用于去重的那一步操作。
小结:
- id如果相同,可以认为是一组,从上往下顺序执行
- 在所有组中,id值越大,优先级越高,越先执行
- 关注点:每一个不同的id号,表示一趟独立的查询,一个sql的查询趟数越少越好
注意:有些情况下,优化器会将子查询优化为关联查询。
因为一般来讲关联查询效果更高,因为子查询会多次遍历数据,而关联查询只遍历一次。
3.select_type
主要是标识某个select查询的属性,小查询在整个大查询中扮演的一个角色。
取值有:SIMPLE、PRIMARY、UNION、UNION RESULT、SUBQUERY、DEPENDENT SUBQUERY、DEPENDENT UNION、DERIVED、MATERIALIZED、UNCACHEABLE SUBQUERY、UNCACHEABLE UNION
4.partitions
代表分区表中的命中情况,非分区表,该项为NULL。一般情况下我们的查询语句的执行计划的partitions列的值都是NULL。
5.type(重要)
执行计划的一条记录就代表着MySQL对某个表执行查询时的访问方法,又称“访问类型”,其中的type列就表明了这个访问方法是啥,是较为重要的一个指标。比如,看到type列的值是ref,表明MySQL即将使用ref访问方法来执行对s1表的查询。
完整的访问方法如下(性能从好到差):null,system,const,eq_ref,ref,fulltext,ref_or_null,index_merge,unique_subquery,index_subquery,range,index,ALL。
- null
查询时不涉及任何表时就为null。
- system
当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问就是system,基本上null和system在业务系统不会出现。
- const
当我们根据主键索引或者唯一索引列与常数进行等值匹配时,对单表的访问方法就是const。性能很好,常数级。
#更具主键索引进行等值比较
explain select * from person where id = 1;
- eq_ref
关联查询时,如果被驱动表是通过主键索引或者唯一索引列等值匹配的方式进行访问的,则对该被驱动表的访问方法为eq_ref。
#主要看被驱动表的连接条件为主键索引
explain select * from person p left join friends f on p.id=f.id
- ref
当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref
#建立普通索引
create index idx_realname on person(realname);
#通过普通索引和常数等值比较
explain select * from person p where realname='admin';
- fulltext
全文索引
- ref_or_null
当对普通索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是ref_or_null。
explain select * from person p where realname='123' or realname is null;
- index_merge
单表访问时某些场景下,可以以多个索引合并的方式进行查询
#不能使用and
explain select * from person p where id=1 or realname='123';
- unique_subquery
针对包含IN子查询的查询语句中,如果优化器决定将IN子查询转为EXISTS子查询,而且子查询可以使用到主键进行等值匹配的话,那么子查询的种类为unique_subquery
- range
使用索引获取某些范围区间的记录,那么就可能使用到range访问方法。如>=、>、in等。
EXPLAIN SELECT * from person where realname >= 'zhang';
- index
使用索引覆盖,但需要扫描全部的索引记录时,就是index。
#创建联合索引
create index idx_username_password on person(username,password);
#不符合最左匹配原则
EXPLAIN SELECT username,password,id from person where password='111';
索引覆盖即联合索引中,虽然不遵守最左匹配原则,但是因为需要查询的字段在联合索引中都会有记录,所以就把联合索引使用上,利用联合索引进行全表搜索。如果要查询的字段有不在联合索引中的情况,就不会是index。因为在这种情况下,主键索引存的数据较多,树相对更高,需要的IO次数也就更多,而联合索引数据只有一部分,IO次数相对较少。
- ALL
全表扫描,性能最差
EXPLAIN SELECT * from person;
除了ALL外,其他的访问方法都能使用到索引
小结:
结果值从最好到最坏依次是:null > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL。
SQL性能优化的目标:至少要达到range级别,要求是ref级别,最好是const级别。
6.possible_keys和key
possible_keys表示执行单表查询时能用到的索引有哪些。key表示实际查询用的是哪一个(少数情况有多个)索引。如果为NULL则没有使用索引。
7.key_len(重要)
实际使用到的索引长度(字节),检查是否充分用到索引,针对联合索引。
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)
示例:
#创建联合索引
create index idx_username_password_age on person(username,password,age);
#key_len为12,因为username字段为VARCHAR(30)非空:30×3+2=92
explain select * from person where username='1';
#key_len为859,因为password字段为VARCHAR(255)非空: 255×3+2 + 92=859。用到了联合索引的2个字段
explain select * from person where username='1' and password='1';
#key_len为864,age字段为int +4。用到了联合索引的3个字段
explain select * from person where username='1' and password='1' and age=1;
#key_len为92,因为只用到了联合索引的username字段
explain select * from person where username='1' and age=1;
通过key_len就可以推断出联合索引中用到的字段,key_len越大越好。
8. ref
与索引列进行等值匹配的对象的信息,有可能是一个常数,可能是一个列,也可能是一个函数。
示例1:
#realname是索引列,与常数'1'进行等值匹配
explain select * from person where realname='1';
示例2:
#关联查询
explain select * from person p1
inner join person p2
on p1.realname = p2.realname;
9.rows
预估的需要读取的记录条目数,值越小越好。rows是没有经过过滤条件筛选的条目数。
10. filtered
经过搜索条件过滤后剩余的记录条数的百分比,即索引查询结果中,我们需要的数据量的占比。
注意关联查询的被驱动表rows为1,表示的是每次循环执行1次,有多次循环,取决于驱动表的rows×filtered次数。
11. Extra
额外的信息,通过该信息,可以更准确的理解MySQL到底将如何执行给定的查询语句。
- Impossible where:条件恒为假,没有意义,如1!=1
- Using where:普通的where过滤条件,如果条件是有索引的,那么Extra就为null
- No matching min/max row:如果有min或max聚合函数,但是并没有符合where添加的记录
- Using index:使用覆盖索引时会出现
- Using index condition:使用到索引下推,如where key1>'2' and key1 like '%a',如果没有索引下推:先查询大于2的,然后再回表,再过滤,取出以a开头的数据;索引下推:先查询大于2的,不回表,先过滤%a,再回表查到最终结果。省去大数据回表。
- Using join buffer (Block Nested Loop):被驱动表没有索引时,会分配一块join buffer的内存加快查询速度
- Not exists:外连接时,被驱动表没有记录
- Using intersect(...) 、 Using union(...) 和 Using sort_union(...):索引合并查询……
- Zero limit:limit要取的值为0
- Using filesort:需要使用文件排序,因为没有索引,需要在内存中进行排序操作
- Using temporary:使用临时表,如distinct或group没有索引的列