MySql高级-索引优化分析(二)

2.1 性能下降SQL慢执行时间长,等待时间长

数据过多:分库分表
关联了太多的表,太多join:SQL优化
没有充分利用到索引:索引建立
服务器调优及各个参数设置:调整my.cnf

2.2 SQL预热:常见通用的Join查询

2.2.1 Join图

在这里插入图片描述

2.2.2 建表SQL

CREATE TABLE `t_dept` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `deptName` VARCHAR(30) DEFAULT NULL,
 `address` VARCHAR(40) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 
CREATE TABLE `t_emp` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `name` VARCHAR(20) DEFAULT NULL,
  `age` INT(3) DEFAULT NULL,
 `deptId` INT(11) DEFAULT NULL,
empno int  not null,
 PRIMARY KEY (`id`),
 KEY `idx_dept_id` (`deptId`)
 #CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 
 
 
INSERT INTO t_dept(deptName,address) VALUES('华山','华山');
INSERT INTO t_dept(deptName,address) VALUES('丐帮','洛阳');
INSERT INTO t_dept(deptName,address) VALUES('峨眉','峨眉山');
INSERT INTO t_dept(deptName,address) VALUES('武当','武当山');
INSERT INTO t_dept(deptName,address) VALUES('明教','光明顶');
 INSERT INTO t_dept(deptName,address) VALUES('少林','少林寺');
 
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('风清扬',90,1,100001);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('岳不群',50,1,100002);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('令狐冲',24,1,100003);
 
 INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('洪七公',70,2,100004);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('乔峰',35,2,100005);
 
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('灭绝师太',70,3,100006);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('周芷若',20,3,100007);
 
 
 
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张三丰',100,4,100008);
 
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张无忌',25,5,100009);
 
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('韦小宝',18,null,100010);
 

2.2.3 7种JOIN

-- 1. 查询所有有门派的人员信息(A、B两表共有)
select * from t_emp a inner join t_dept b on a.deptId = b.id;
 
-- 2.列出所有用户,并显示其机构信息(A的全集)
select * from t_emp a left join t_dept b on a.deptId = b.id;
 
-- 3.列出所有门派(B的全集)
select * from t_dept b;
 
-- 4.所有不入门派的人员(A的独有)
select * from t_emp a left join t_dept b on a.deptId = b.id where b.id is null;
 
-- 5.所有没人入的门派(B的独有)
select * from t_dept b left join t_emp a on a.deptId = b.id where a.deptId is null;
 
-- 6.列出所有人员和机构的对照关系(AB全有)
#MySQL Full Join的实现 因为MySQL不支持FULL JOIN,下面是替代方法
#left join + union(可去除重复数据)+ right join
SELECT * FROM t_emp A LEFT JOIN t_dept B ON A.deptId = B.id
UNION
SELECT * FROM t_emp A RIGHT JOIN t_dept B ON A.deptId = B.id
 
-- 7.列出所有没入派的人员和没人入的门派(A的独有+B的独有)
SELECT * FROM t_emp A LEFT JOIN t_dept B ON A.deptId = B.id WHERE B.`id` IS NULL
UNION
SELECT * FROM t_emp A RIGHT JOIN t_dept B ON A.deptId = B.id WHERE A.`deptId` IS NULL;
 
-- 增加掌门字段
ALTER TABLE `t_dept` add  CEO  INT(11)  ;
 
update t_dept set CEO=2 where id=1;
update t_dept set CEO=4 where id=2;
update t_dept set CEO=6 where id=3;
update t_dept set CEO=8 where id=4;
update t_dept set CEO=9 where id=5;
 
-- 8.求各个门派对应的掌门人名称:
select * from t_dept as  b left  join t_emp as a on  b.CEO=a.id;
 
-- 9.求所有当上掌门人的平均年龄
select avg(a.age) from t_emp a inner join t_dept b on a.id=b.CEO  ;
 
-- 10.求所有人物对应的掌门名称
-- 方法一
select c.name, ab.name ceoname from t_emp c left join (select b.id, a.name from t_emp a inner join t_dept b on b.ceo = a.id) ab on c.deptId = ab.id;
-- 方法二
select ab.name, c.name ceoname from (select a.name, b.CEO from t_emp a left join t_dept b on a.deptId=b.id) ab left join t_emp c on ab.ceo=c.id;
-- 方法三
select a.name, c.name ceoname from t_emp a
left join t_dept b on a.deptId = b.id
left join t_emp c on b.CEO = c.id;
-- 方法四
select a.name, (select c.name from t_emp c where c.id=b.CEO) ceoname from t_emp a
left join t_dept b on a.deptId= b.id;
 

2.3 索引简介

2.3.1 是什么

Mysql官方对索引的定义为:索引(Index)是帮助Mysql高效获取数据的数据结构。可以得到索引的本质:索引是数据结构

索引的目的在于提高查询效率,可以类比字典,
如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从上往下找到y字母,再找到剩下的sql。
如果没有索引,那么你可能需要a----z,如果我想找到Java开头的单词呢?或者Oracle开头的单词呢?
是不是觉得如果没有索引,这个事情根本无法完成?

可以简单立即为"排好序的快速查找数据结构"
在这里插入图片描述
数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。

一般来说索引本身也很大,不可能全部存储在内存种,因此索引往往以索引文件的形式存储在磁盘上。

2.3.2 优势

类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本。

通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗

2.3.3 劣势

虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行insert、update、和delete。因为更新表时,mysql不仅要保存数据,还要保存一下索引文件,每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。

实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。

2.4 mysql索引结构

2.4.1 BTree索引

1.原理图
在这里插入图片描述
在这里插入图片描述
2.时间复杂度
在这里插入图片描述

2.4.2 B+Tree索引

1.原理图
在这里插入图片描述
2.B树和B+树的区别

  • 1)B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
  • 2)在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;
    而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看B-树的性能好像要比B+树好,而在实际应用中却是B+树的性能要好些。
    因为B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比B-树多,树高比B-树小,这样带来的好处是减少磁盘访问次数。
    尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些,而且B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用B+树的缘故。

思考:为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引?

  • B+树的磁盘读写代价更低

B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

  • B+树的查询效率更加稳定

由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

2.4.3 聚簇索引与非聚簇索引

在这里插入图片描述

2.5 索引分类

2.5.1 单值索引

即一个索引只包含单个列,一个表可以有多个单列索引

-- 随表一起建索引
create table customer(
	id int(10) unsigned auto_increment,
	customer_no varchar(200),
	customer_name varchar(200),
	primary key(id), 
    key(customer_name)
);
 
-- 单独建单值索引
create index idx_customer_name on customer(customer_name);
 
-- 删除索引
drop index idx_customer_name on customer;

2.5.2 唯一索引

索引列的值必须唯一,但允许有空值

-- 随表一起建索引
create table customer(
	id int(10) unsigned auto_increment,
	customer_no varchar(200),
	customer_name varchar(200),
	primary key(id), 
    key(customer_name),
	unique(customer_no)
);
 
-- 单独建唯一索引
create unique index idx_customer_no on customer(customer_no);
 
-- 删除索引
drop index idx_customer_no on customer;

2.5.3 主键索引

设定为主键后数据库会自动建立索引,innodb为聚簇索引

-- 随表一起建索引
create table customer(
	id int(10) unsigned auto_increment,
	customer_no varchar(200),
	customer_name varchar(200),
	primary key(id)
);
 
-- 单独建主键索引
alter table customer add primary key customer(customer_no);
 
-- 删除主键索引
alter table customer drop primary key;
 
-- 修改主键索引
必须先删除掉(drop)原索引,再新建(add)索引

2.5.4 复合索引

即一个索引包含多个列

-- 随表一起建索引
create table customer(
	id int(10) unsigned auto_increment,
	customer_no varchar(200),
	customer_name varchar(200),
	primary key(id), 
	key(customer_name),
	unique(customer_no)
	key(customer_no,customer_name)
);
 
-- 单独建索引
create index idx_no_name on customer(customer_no,customer_name);
 
-- 删除索引
drop index idx_no_name on customer;

2.5.5 基本用法

-- 创建
create [unique] index [indxName] on table_name(column))
-- 删除
drop index[indexName] on mytable;
-- 查看
show index from table_name;
-- 使用alter命令
-- 1.该语句添加一个主键,这意味着索引必须是唯一的,且不能为null
alter table tbl_name add primary key(column_list);
-- 2.这条语句创建索引的值必须是唯一的(除了null外,null可能会出现多次)
alter table tbl_name add unique index_name(column_list);
-- 3.添加普通索引,索引值可出现多次
alter table tbl_name add index index_name(column_list);
-- 4.该语句指定了索引为fulltext,用于全文索引
alter table tbl_name add fulltext index_name(column_list);

2.5.6 哪些情况需要创建索引

主键自动建立唯一索引
频繁作为查询条件的字段应该创建索引
查询种与其它表关联的字段,外键关系建立索引
单键/组合索引的选择问题,组合索引性价比更高
查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
查询中统计或者分组字段

2.5.7哪些情况不要创建索引

表记录太少
经常增删改的表或者字段
where条件里用不到的字段不创建索引
过滤性不好的不适合建索引

2.6 性能分析Explain

2.6.1 是什么(查看执行计划)

使用explain关键字可以模拟优化器执行sql查询语句,从而知道mysql是如何处理你的sql语句的。分析你的查询语句或是表结构的性能瓶颈。

官网介绍

http://dev.mysql.com/doc/refman/5.5/en/explain-output.html
在这里插入图片描述

2.6.2 能干吗

表的读取顺序
哪些索引可以使用
数据读取操作的操作类型
哪些索引被实际使用
表之间的引用
每张表有多少行被物理查询

2.6.3 怎么玩

1.Explain+SQL语句

2.执行计划包含的信息
在这里插入图片描述
3.建表脚本

 CREATE TABLE t1(id INT(10) AUTO_INCREMENT,content  VARCHAR(100) NULL ,  PRIMARY KEY (id));
 CREATE TABLE t2(id INT(10) AUTO_INCREMENT,content  VARCHAR(100) NULL ,  PRIMARY KEY (id));
 CREATE TABLE t3(id INT(10) AUTO_INCREMENT,content  VARCHAR(100) NULL ,  PRIMARY KEY (id));
 CREATE TABLE t4(id INT(10) AUTO_INCREMENT,content  VARCHAR(100) NULL ,  PRIMARY KEY (id));
 
 
 INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));
 
  INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));
  
  INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));
    
  INSERT INTO t4(content) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)));

2.6.4 各字段解释

1.id

select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序。

三种情况

id相同,执行顺序由上至下
在这里插入图片描述
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
在这里插入图片描述
id相同不同,同时存在
在这里插入图片描述
关注点

id号每个号码,表示一趟独立的查询。一个sql的查询趟数越少越好。

2.select_type

查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询
在这里插入图片描述
simple:简单的select查询,查询中不包含子查询或者union
在这里插入图片描述
primary:查询中若包含任何复杂的子部分,最外层查询则被标记为primary
在这里插入图片描述
derived:在from列表中包含的子查询被标记为derived(衍生),mysql会递归执行这些子查询,把结果放在临时表里。在这里插入图片描述
subquery:在select或where列表中包含了子查询在这里插入图片描述
dependent subquery:在select或where列表中包含了子查询,子查询基于外层

in的区别
在这里插入图片描述
uncacheable subquery:不可用缓存的子查询,但凡sql一模一样就可以命中缓存,sql肯定会不一样就不能命中缓存。只要sql中包含系统变量,这块随时都会有变化,默认这种sql为不可用缓存

@@var:系统变量

show variables like '%lower_case_table_names%';
select @@lower_case_table_names from dual;

在这里插入图片描述
union:若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为:derived
在这里插入图片描述
union result:从union表获取结果的select
在这里插入图片描述
3.table

显示这一行的数据是关于哪张表的

4.partitions

代表分区表中的命中情况,非分区表,该项为null
在这里插入图片描述
5.type
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一般来说,得保证查询至少达到range级别,最好能达到ref。

system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计。在这里插入图片描述
const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,Mysql就能将该查询转换为一个常量

在这里插入图片描述
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。在这里插入图片描述
ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。在这里插入图片描述
在这里插入图片描述
range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询,这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。在这里插入图片描述index:出现index是sql使用了索引,但是没有通过索引进行过滤,一般是使用了覆盖索引或者是利用索引进行了排序分组。
在这里插入图片描述
all:Full Table Scan,将遍历全表以找到匹配的行在这里插入图片描述
index_merge:在查询过程中需要多个索引组合使用,通常出现在有or的关键字的sql中在这里插入图片描述
ref_or_null:对于某个字段既需要关联条件,也需要null值的情况下。查询优化器会选择用ref_or_null连接查询在这里插入图片描述
index_subquery:利用索引来关联子查询,不再全表扫描。在这里插入图片描述
unique_subquery:该连接类型类似于index_subquery。子查询中的唯一索引在这里插入图片描述
6.possible_keys

显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。

7.key

实际使用的索引,如果为null,则没有使用索引,查询中若使用了覆盖索引,则该索引和查询的select字段重叠

在这里插入图片描述
8.key_len

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。

key_len字段能够帮你检查是否充分的利用上了索引,值越大,说明使用到的索引越全。但在不影响查询效率和查询结果的前提下,值越小越好。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
9.ref

显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值在这里插入图片描述
10.rows

rows列显示mysql认为它执行查询时必须检查的行数。在这里插入图片描述
越少越好
11.filtered

这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数

12.Extra

包含不适合在其他列中显示但十分重要的额外信息

Using filesort:说明myql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。mysql中无法利用索引完成的排序操作称为文件排序
在这里插入图片描述
在这里插入图片描述
上述截图的优化方案分析:

  1. 分析:第一张截图由possible_key可以看出当前表仅建立了字段deptno单值索引,而ename字段上未建立索引,因此order by ename就会产生文件排序。

  2. 方案给deptno和ename加上复合索引,即可解决。

Using temporary:使用了临时表保存中间结果,mysql在对查询结果排序时,使用临时表。常见于排序order by和分组查询group by。在这里插入图片描述
在这里插入图片描述
上述截图优化分析:
截图一可见,当前表仅有deptno字段加了索引,sql使用了grop by ename,因此产生了using temporary;using filesort。
截图二可见,重新加上了复合索引(deptno,ename),效果是去掉了截图一产生的,并额外获得了using index(索引覆盖)

Using index:表示相应的select操作中使用了覆盖索引(Covering index),避免访问了表的数据行,效率不错!如果同时出现using where,表名索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引只是用来读取数据而非利用索引执行查找。利用索引进行了排序或分组。

Using where:表明使用where过滤。

Using join buffer:使用了连接缓存。
在这里插入图片描述
impossible where:where子句的值总是false,不能用来获取任何元组。在这里插入图片描述
select tables optimized away:在没有group by子句的情况下,基于索引优化min/max操作或者对于myisam存储引擎优化count(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值