mysql query optimizer
mysql中有专门负责优化 select 语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为窗户端请求的query提供它认为最优的执行计划(不一定是DBA认为最优的,这部分最耗费时间)
当客户端向mysql发送一条query,命令解析器模块完成请求分类,区别出是select并转发给 mysql query optimizer时,mysql query optimizer首先会对整条query进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对query中的查询条件进行简化和调整,如去掉一些无用或显而易见的条件、结构调整等。然后分析query中的hint信息(如果有),看显示hint信息是否可以完全确定该query的执行计划。如果没有hint或hint信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据query进行写相应的计算分析,然后再得出最后的执行计划。
mysql 常见瓶颈
- CPU : CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据的时候
- IO : 磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
- 服务器硬件的性能瓶颈 : top,free,iostat和vmstat来查看系统的性能状态
explain
explain可以模拟优化器执行sql查询语句,从而知道mysql是如何处理sql语句的,分析查询或表结构的性能瓶颈
可以分析:
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被优化器查询
测试sql
-- 用户表
CREATE TABLE `user_info` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL DEFAULT '',
`age` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name_index` (`name`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8;
INSERT INTO user_info (name, age) VALUES ('xys', 20);
INSERT INTO user_info (name, age) VALUES ('a', 21);
INSERT INTO user_info (name, age) VALUES ('b', 23);
INSERT INTO user_info (name, age) VALUES ('c', 50);
INSERT INTO user_info (name, age) VALUES ('d', 15);
INSERT INTO user_info (name, age) VALUES ('e', 20);
INSERT INTO user_info (name, age) VALUES ('f', 21);
INSERT INTO user_info (name, age) VALUES ('g', 23);
INSERT INTO user_info (name, age) VALUES ('h', 50);
INSERT INTO user_info (name, age) VALUES ('i', 15);
-- 收货地址表
create table user_address(
id bigint(20) not null auto_increment,
user_id bigint(20),
address varchar(50) not null default '',
default_add int default 0,
primary key (id)
)ENGINE = InnoDB DEFAULT CHARSET = utf8;
insert into user_address (user_id,address,default_add) values(1,'上海',1);
insert into user_address (user_id,address,default_add) values(1,'北京',0);
insert into user_address (user_id,address,default_add) values(1,'杭州',0);
insert into user_address (user_id,address,default_add) values(2,'上海',1);
insert into user_address (user_id,address,default_add) values(2,'郑州',0);
-- 商品表
create table goods(
id bigint(20) not null auto_increment,
price int not null default 0,
gname varchar(50) not null default '',
primary key (id)
)ENGINE = InnoDB DEFAULT CHARSET = utf8;
insert into goods(price,gname) values(23,'钢笔');
insert into goods(price,gname) values(14,'笔记本');
insert into goods(price,gname) values(2300,'电子琴');
insert into goods(price,gname) values(350,'足球');
insert into goods(price,gname) values(135,'电饭锅');
-- 订单表
CREATE TABLE `order_info` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`user_id` BIGINT(20) DEFAULT NULL,
goods_id bigint(20) default null,
address_id bigint(20) default null,
PRIMARY KEY (`id`)
)ENGINE = InnoDB DEFAULT CHARSET = utf8;
insert into order_info(user_id,goods_id,address_id) values(1,1,1);
insert into order_info(user_id,goods_id,address_id) values(1,2,2);
insert into order_info(user_id,goods_id,address_id) values(1,2,3);
insert into order_info(user_id,goods_id,address_id) values(2,4,4);
insert into order_info(user_id,goods_id,address_id) values(2,4,4);
insert into order_info(user_id,goods_id,address_id) values(2,5,5);
使用方法
explain 后面跟需要分析的 sql 语句
mysql> select * from user_info;
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | xys | 20 |
| 2 | a | 21 |
| 3 | b | 23 |
| 4 | c | 50 |
| 5 | d | 15 |
| 6 | e | 20 |
| 7 | f | 21 |
| 8 | g | 23 |
| 9 | h | 50 |
| 10 | i | 15 |
+----+------+------+
10 rows in set (0.00 sec)
mysql> explain select * from user_info;
+----+-------------+-----------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | user_info | ALL | NULL | NULL | NULL | NULL | 10 | NULL |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------+
1 row in set (0.00 sec)
mysql>
id
select 查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
三种情况
- id相同,执行顺序由上至下
explain
select
u. name as '用户名',
ua.address as '收货地址',
g.gname as '商品名称'
from
user_info u
left join user_address ua on u.id = ua.user_id
left join order_info o on ua.id = o.address_id
left join goods g on o.goods_id = g.id
where
u.id = 1;
+--------+----------+----------+
| 用户名 | 收货地址 | 商品名称 |
+--------+----------+----------+
| xys | 上海 | 钢笔 |
| xys | 北京 | 笔记本 |
| xys | 杭州 | 笔记本 |
+--------+----------+----------+
+----+-------------+-------+------------+--------+---------------+---------+---------+------------------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | u | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100 | NULL |
| 1 | SIMPLE | ua | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100 | Using where |
| 1 | SIMPLE | o | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | g | NULL | eq_ref | PRIMARY | PRIMARY | 8 | etest.o.goods_id | 1 | 100 | NULL |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------------+------+----------+----------------------------------------------------+
- id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
- id相同不同,同时存在
id如果相同,可以认为是一组,从上往下顺序执行
在所有主中,id只越大,优先级越高,越先执行
select_type
参考:https://blog.csdn.net/guangzixin/article/details/8200296
主要用于区分查询的类型,如:普通查询、联合查询、子查询等复杂查询
- SIMPLE
简单的SELECT,不实用UNION或者子查询
explain select * from user where uid=1;
- PRIMARY
表示主查询,或者是最外面的查询语句
explain select * from (select * from user where uid=1)b
3. UNION
第二层,在SELECT之后使用了UNION
explain select * from user where uid=1 union select * from user where uid=2
4. DEPENDEN UNION
UNION语句中的第二个SELECT,依赖于外部子查询
explain select * from user x where uid in (select uid from user y union select uid from user z where uid<5)
5. UNION RESULT
UNION的结果
explain select * from user where uid=1 union select * from user where uid=2
6. SUBQUERY
子查询中的第一个SELECT
explain select * from groups where gid =(select gid from user where uid=1)
7. DEPENDENT SUBQUERY
子查询中的第一个SELECT,取决于外面的查询
explain select * fromuser where uid in (select uid from user where uid<4)
8. DERIVED
导出表的SELECT(FROM子句的子查询)
explain select * from (select * from user where uid=1)b
table
表名
type
参考:https://blog.csdn.net/it_0101/article/details/77868939
表示表的连接类型
八种情况【5.7有14种】?:
从最好到最差依次是:
system>const>eq_ref>ref>range>index>All
-
system
表示只有一行记录(等于系统表),这是const类型的特例,平时不会出现,也可以忽略不计 -
const
如sql语句“WHERE user_id>=125 and sid = 377 and compact_state >= 1”当where条件中只要某一单一条件使用了主键(默认唯一索引)或者自建立的唯一索引并且参数在索引中有值(如sid=111,表中必须有sid为111的记录,若没有这么一条记录,Extra列会出现“no matching row in const table”,type为null表示为找到记录,如下图),则type为const.
优化点:若字段是数字 int bigint double 等数据类型,参数尽量不要加引号,而若是char varchar 等字符串类型,最好加上引号,如下两图对比。
其中compact_code为varchar类型,且建立了一个unique类型索引,图1可以看出,正常加上引号后,type为const(常量,效率最佳),并且用到了建立的unique索引idx_code,rows为1(代表预估算需要扫描多少条记录,这个估算并不准确);
图2,去掉引号后,type为all(全表扫描,效率最差),且并未用到索引,rows为135(代表估算需要扫描135条记录)
3. eq_ref
如图:其中a.sid为a表的主键,b表为a表的附表,compact_id对应a.sid,这种连表查询时,即先以主键a.sid查找a表记录,找到索引树上第一个值后,再去扫描b表全部,再接着第二遍…,所以a表为eq_reg(比const差一点(const是去直接找到那一条),比ref好一点(ref为普通索引,找到第一条后还要继续找)),b表为all(全表扫描)。
- ref
使用到普通索引(非unique)查询非范围时出现,如compact_code = “123”,compact_code为普通索引。
- range
如sql语句“where 字段1 >= “a” and 字段2 >= 281 and 字段3 >= “1””,只要其中一个字段使用了索引(不管是unique、normal、full text)或者主键,查询某一范围的数据时出现,(当然,这里排除了const的情况,即满足不了const出现的条件)
- index
情况1、只查询索引列时出现(因为索引是已经排序好的,第二图已说明,实际数据并未排序好),不管索引是什么索引
情况2、order by 主键(只能是主键,即使是其他unique索引也不行,第二图已说明)
- All
全表扫描,从头到尾扫描整张表查找行,效率最差
possible_keys
key
key_len
ref
rows
Extra
done