数据库索引简介及优化

索引

1.索引简介

1.1 概念

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

注:在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。你可以简单理解为“排好序的快速查找数据结构。

1.2 索引存储位置和文件结构

一般来说索引本身也很大,不可能存储在内存中,因此索引往往以文件形式存储在硬盘上

索引文件结构:hash、二叉树、B树、B+树

其中聚簇(合)索引,次要索引,覆盖索引,复(连)合索引,前缀索引,唯一索引默认都是使用B+树索引文件结构, memory的存储引擎使用的是hash结构

1.3 索引优势和劣势

优势:

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

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

劣势:

1)虽然索引大大提高了查询速度,同时却会降低更新表的速度,如果对表INSERT,UPDATE和DELETE。因为更新表时,MySQL不仅要修改存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息

2)索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立优秀的索引,或优化查询语句

1.4 mysql的索引文件

在msql5.6版本中,如果是MyIsam引擎:那么一个表有三个文件,如:C:\dev\mysql\data\mysql\下的columns_priv表,就有columns_priv.frm(表结构文件),columns_priv.MYD(表数据文件),columns_priv.MYI(表索引文件)

在msql5.6版本中,如果是InnoDB引擎:对于每一个表,都有一个xxx.frm的表结构文件和xxx.ibd的文件,这个xxx.ibd文件就是存放表的数据和表的索引的文件,也就是表的索引文件和数据文件保存到了一个文件中,注意:xxx.ibd文件默认是隐藏的,如:C:\ProgramData\MySQL\MySQL Server 5.5\data\hospital目录里面我们只能看到hospital库的表的结构文件,看不到ibd文件。

show variables like '%per_table%'   #可以发现默认是off
set @@global.innodb_file_per_table=on;

另外在mysql8的版本中,show variables like '%per_table%'默认是on,另外在myql8中,并不单独提供xxx.frm表结构文件,而是合并在xxx.ibd文件中,Oracle官方将frm文件的信息以及更多信息移动到叫做序列化字典(SDI),SDI倍写在ibd文件内部

2.索引分类

2.1 单值索引

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

案例:

  • 创建索引,并指定主键索引
mysql> CREATE TABLE customer (id INT(10)  PRIMARY KEY AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200)
);
  • 创建单值索引
mysql> create index idx_customer_name ON customer(customer_name)
  • 查询索引
mysql> show index from customer;   #这个表其实就有2个索引
  • 删除索引
DROP INDEX idx_customer_name on customer ;

2.2 唯一索引

如果当前表中字段添加了唯一性约束,mysql主动的将当前字段上的数据进行排序,其生成的索引就是唯一索引,索引列的值必须唯一,但允许有空值

CREATE TABLE customer (id int primary key not null auto_increment,
customer_no varchar(16),customer_name VARCHAR(200),UNIQUE key(customer_no)
);

###单独建唯一索引:
create unique index idx_customer_no ON customer(customer_no); 

注:建立唯一索引时必须保证所有的值是唯一的(除了null),若有重复数据,会报错。

2.3 复合索引

在数据库操作期间,用户可以在多个列上建立索引,这种索引叫做复合索引(组合索引),即一个索引包含多个列,复合索引比单值索引所需要的开销更小(对于相同的多个列建索引)

create table customer(id int(10) UNSIGNED primary key auto_increment,customer_no varchar(200),customer_name varchar(200),unique key(customer_name),key(customer_no,customer_name))
 
单独建索引:
create index idx_no_name on customer(customer_no,customer_name);

2.4 主键索引

如果当前表中字段添加了主键约束,mysql主动的将当前字段上数据进行排序,其生成的索引就是主键索引,设定为主键后数据库会自动建立索引,在innodb引擎中,这种索引也是聚簇索引结构

create table customer(id int(10) UNSIGNED auto_increment,customer_no varchar(200),customer_name varchar(200),primary key(id))

注:使用 AUTO_INCREMENT 关键字的列必须有索引(只要有索引就行)。

2.5前缀索引

所谓前缀索引说白了就是对文本的前几个字符建立索引(具体是几个字符在建立索引时指定),这样建立起来的索引更小,所以查询更快。这有点类似于 Oracle 中对字段使用 Left 函数来建立函数索引,只不过 MySQL 的这个前缀索引在查询时是内部自动完成匹配的,并不需要使用 Left 函数。

alter table system_user add index user_uuid_index(user_uuid(10));

3.索引语法

3.1 创建索引

1)CREATE [UNIQUE] INDEX 索引名 ON 表名(列名);

2)ALTER TABLE 表名 ADD [UNIQUE] INDEX [索引名] ON(列名);

ALTER有四种方式来添加数据表的索引:
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次。
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。

3.2 查看索引

show index from 表名

3.3 删除索引

DROP INDEX 索引名 ON 表名;

4.索引结构

4.1 BTREE树结构

4.2 hash结构

是指使用某种哈希函数实现key->value 映射的索引结构

Hash索引只有Memory, NDB两种引擎支持,Memory引擎默认支持Hash索引,如果多个hash值相同,出现哈希碰撞,那么索引以链表方式存储。NoSql采用此中索引结构。由于HASH的唯一(几乎100%的唯一)及类似键值对的形式,很适合作为索引。HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在**“=”和“in”条件下高效**,对于范围查询、排序及组合索引仍然效率不高

因此,哈希索引适用于等值检索,通过一次哈希计算即可定位数据的位置

image-20210706194207799

4.3 full-text全文索引

全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用【分词技术】等多种算法智能分析出文本文字中关键词的频率和重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果

drop table article;
create table article(
 id int(10) UNSIGNED not null auto_increment,
 title varchar(200) default null,
 content text,
 primary key(id),
 FULLTEXT key(title,content)
)ENGINE=MyISAM DEFAULT CHARSET=utf8;

不同于like方式的的查询:SELECT * FROM article WHERE content LIKE ‘%查询字符串%’;

全文索引用match+against方式查询:

SELECT * FROM article WHERE MATCH(title,content) AGAINST (‘查询字符串’); 明显的提高查询效率。它的出现是为了解决WHERE name LIKE “%word%"这类针对文本的模糊查询效率较低的问题。

注意:mysql5.6.4以前只有Myisam支持,5.6.4版本以后innodb才支持,但是官方版本不支持中文分词,需要第三方分词插件。5.7以后官方支持中文分词。随着大数据时代的到来,关系型数据库应对全文索引的需求已力不从心,逐渐被 solr,elasticSearch等专门的搜索引擎所替代。在全文检索中,又分正排索引和倒排索引,也就是说正排索引和倒排索引,针对是全文的检索,MySQL既不是倒排索引,也不是正排索引

4.4 R-Tree索引(了解)

R-Tree在mysql很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有myisam、bdb、innodb、ndb、archive几种。相对于b-tree,r-tree的优势在于范围查找

4.5 聚簇索引与非聚簇索引区别

mysql的索引类型跟存储引擎是相关的,innodb存储引擎数据文件跟索引文件全部放在ibd文件中,而myisam的数据文件放在扩展名为myd文件中,索引放在扩展名为myI文件中,其实区分聚族索引和非聚族索引非常简单,只要判断数据跟索引是否存储在一起就可以了。
innodb存储引擎在进行数据插入的时候,数据必须要限索引放在一起,如果有主键就使用主键,没有主键就使用唯一键,没有唯一键就使用6字节的rowid,因此跟数据绑定在一起的就是聚簇索引
, 而为了避免数据冗余存储,其他的索引的叶子节点中存储的都是聚族索引的key值,因此innodb中既有聚簇索引也有非聚簇索引,而myisam中只有非聚簇索引。

注意事项:

1)、在innodb引擎中,一个表最多只能有一个聚簇索引,一个表也必须有一个聚簇索引
2)、只有innodb数据引擎支持聚簇索引,myisam不支持,在innodb中,采用主键作为聚簇索引
3)、如果当前表中没有主键,mysql将会选择一个唯一性的字段作为聚簇索引
4)、如果当前表既没有主键字段,也没有添加唯一约束字段,mysql将随机选取一个字段作为聚簇索引
5)、在表中其它字段上创建的索引都是非聚簇索引

5.如何合理创建索引

5.1 建议创建索引

1.主键自动建立唯一索引

2.频繁作为查询的条件的字段应该创建索引

3.查询中与其他表关联的字段,外键关系建立索引

4.查询中排序的字段,排序字段若通过索引去访问将大大提高排序的速度

5.查询中统计或者分组字段

5.2 不建议创建索引

1.表记录太少

2.经常增删改的表

3.数据重复且分布平均的表字段,因此应该只为经常查询和经常排序的数据列建立索引。

4.频繁更新的字段不适合创建索引(因为每次更新不单单是更新了记录还会更新索引,加重IO负担)

注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果

6.mysql性能分析Explain

在Mysql中,有一个专门负责优化SELECT语句的优化器模块(Mysql Query optimizer(优化)) ,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划 ,当客户端向MySQL请求一条Query语句到命令解析器模块完成请求分类区别出是SELECT并转发给QueryOptimizer之后,QueryOptimizer首先会对整条Query进行优化处理掉一些常量表达式的预算,直接换算成常量值。并对Query中的查询条件进行简化和转换,如去掉一些无用或者显而易见的条件,结构调整等等。然后则是分析Query中的Hint信息(如果有),看显示Hint信息是否可以完全确定该Query的执行计划。如果没有Hint或者Hint信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据Query进行写相应的计算分析,然后再得出最后的执行计划

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

explain select * from mysql.user;

image-20210706195423604

6.1 id列

id:包含一组数字,表示查询中执行select子句或操作表的顺序,如果id相同,执行顺序由上至下,可以认为是一组,从上往下顺序执行,在所有组中,id值越大,优先级越高,越先执行;如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

案例1:id值相同,从上到下执行

explain select * from employee inner join department on employee.departmentid = department.id;

image-20210706200915376

案例2:id值越大,优先级越高,越先执行

mysql> explain select * from employee where departmentid = (select id from department where name = (select name from `order` where id = 1));

image-20210706202628569

案例3:

mysql> explain select s1.* from (select * from employee) s1  inner join department d on  s1.departmentid = d.id

image-20220225153543054

6.2 select_type列

表示查询中每个select子句的类型:SIMPLE、PRIMARY、SUBQUERY、DERIVED、UNION、UNION RESULT

  • SIMPLE:查询中不包含子查询或者UNION
  • PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为
  • SUBQUERY:在SELECT或WHERE列表中包含了子查询,该子查询被标记为SUBQUERY
  • DERIVED:在FROM列表中包含的子查询被标记为:DERIVED(衍生)
  • UNION:若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
  • 从UNION表获取结果的SELECT被标记为:UNION RESULT

案例1:union的例子

mysql> explain select * from employee e inner join department d on e.departmentid = d.id union select * from employee e1 inner join department d1 on e1.departmentid = d1.id;

image-20210706204336162

6.3 table列

显示这一行数据时关于哪个表的

6.4 type列

表示MySQL在表中找到所需行的方式,又称“访问类型”

type 扫描方式由快到慢:system>const>eq_ref>ref>range>index>ALL

6.4.1 system

它是const联接类型的特例,表只有一行记录(等于系统表),很少出现

6.4.2 const

通过索引一次就能找到,速度非常快。const用于比较【primary key或者unique索引】,被连接的部分是一个常量值(const),只匹配一行数据,所以很快。通常将主键至于where列表中

explain select * from employee where id = 1;

6.4.3 eq_ref

对于前表中的每一行(row),对应后表只有一行被扫描,这类扫描的速度也非常的快。应用场景:1.联表(join)查询;2.命中主键或者非空唯一索引;3.等值连接

案例:

explain select * from user2 left join user_balance on user2.id = user_balance.uid 
###########################
####前表
create table user2 (
  id int primary key,
  name varchar(20)
)engine=innodb;
insert into user2 values(1,'ar414');
insert into user2 values(2,'zhangsan');
insert into user2 values(3,'lisi');
insert into user2 values(4,'wangwu');
############后表###################
create table user_balance (
  uid int primary key,
  balance int
)engine=innodb;
insert into user_balance values(1,100);
insert into user_balance values(2,200);
insert into user_balance values(3,300);
insert into user_balance values(4,400);
insert into user_balance values(5,500);

6.4.4 ref

非唯一性索引扫描,返回匹配某个单独值的所有行,场景:联表查询普通非唯一索引

案例:

explain select * from user1 left join user_balance on user1.id = user_balance.uid  
##############################################
create table user1 (
  id int primary key,
  name varchar(20)
)engine=innodb;

insert into user values(1,'ar414');
insert into user values(2,'zhangsan');
insert into user values(3,'lisi');
insert into user values(4,'wangwu');
#################################################
create table user_balance (
  uid int,
  balance int,
  index(uid)  #########普通索引
)engine=innodb;

insert into user_balance values(1,100);
insert into user_balance values(2,200);
insert into user_balance values(3,300);
insert into user_balance values(4,400);
insert into user_balance values(5,500);

由于后表使用了普通非唯一索引,对于前表user1表的每一行(row),后表user_balance表可能有多于一行的数据被扫描

案例2:

create index idx_col1_col2 on employee(name);
explain select * from employee where name ='ac';

6.4.5 range

只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询,这种范围扫描索引扫描比全表扫描要好,因为他只需要开始索引的某一点,而结束语另一点,不用扫描全部索引

案例1:

explain select * from employee where id between 1 and 10;

image-20210706212805287

6.4.6 index

index与ALL区别为index类型只遍历索引树。这通常比ALL快,只快一点,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)
案例1:

explain select id from employee;

image-20210706213101144

6.4.7 All

all:Full Table Scan, MySQL将遍历全表以找到匹配的行

案例1:

explain select * from employee;

总结:一般来说,得保证查询只是达到range级别,最好达到ref

总结:一般来说,得保证查询只是达到range级别,最好达到ref

总结:一般来说,得保证查询只是达到range级别,最好达到ref

6.5 possible_keys列

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

explain select * from employee where name='1';

image-20210706213624564

6.6 key列

实际使用的索引,如果为null则没有使用索引,如果不为null,则表示使用了索引,有可能使用了覆盖索引

覆盖索引:只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快

案例1:覆盖索引

create table user(
id int(10) auto_increment,
name varchar(30),
age tinyint(4),
primary key (id),
index idx_age (age)
)engine=innodb charset=utf8mb4;
##################################################
insert into user(name,age) values('张三',30);
insert into user(name,age) values('李四',20);
insert into user(name,age) values('王五',40);
insert into user(name,age) values('刘八',10);

id 字段是聚簇索引,age 字段是普通索引(二级索引)

1.索引的存储结构:id 是主键,所以是聚簇索引,其叶子节点存储的是对应行记录的数据

image-20220225165326776

如果查询条件为主键(聚簇索引),则只需扫描一次B+树即可通过聚簇索引定位到要查找的行记录数据。

select * from user where id = 1;

image-20220225165747757

2.age 是普通索引(二级索引),非聚簇索引,其叶子节点存储的是聚簇索引的的值

image-20220225165513996

如果查询条件为普通索引(非聚簇索引),需要扫描两次B+树,第一次扫描通过普通索引定位到聚簇索引的值,然后第二次扫描通过聚簇索引的值定位到要查找的行记录数据,这种也叫回表操作,所谓回表操作:先通过普通索引的值定位聚簇索引值,再通过聚簇索引的值定位行记录数据,需要扫描两次索引B+树,它的性能较扫一遍索引树更低

select * from user where age = 30;

1.先通过普通索引 age=30 定位到主键值 id=1

2.再通过聚集索引 id=1 定位到行记录数据

image-20220225170317390

3.覆盖索引:只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快

select id,age from user where age = 10;

使用覆盖索引的常见的方法是:将被查询的字段,建立到联合索引里去

如:select id,age from user where age = 10;

思考如下sql用到了索引吗?覆盖索引吗?为什么?

select id,age,name from user where age = 10;

age是普通索引,但name列不在索引树上,所以通过age索引在查询到id和age的值后,需要进行回表再查询name的值,用到了索引,但是没用到覆盖索引。此时的Extra列的值为NULL表示进行了回表查询

改造:为了实现索引覆盖,需要建组合索引idx_age_name(age,name)

drop index idx_age on user;
create index idx_age_name on user(`age`,`name`);

explain分析:此时字段age和name是组合索引idx_age_name,查询的字段id、age、name的值刚刚都在索引树上,只需扫描一次组合索引B+树即可,这就是实现了索引覆盖,此时的Extra字段为Using index表示使用了索引覆盖

案例2:理论和实际都用到了索引

insert into employee(id,name,departmentid) values(5,'xxx',1);
explain select id ,name from employee where id  =1;

image-20210706214713216

理论上和实际上都用到了索引

6.7 key_len列

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好,key_len显示的值为索引最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的,如果实际上用到索引,则有索引的长度,否则为null

select * from employee where id = 1;

image-20210706214920346

6.8 ref列

显示使用哪个列或常数与key一起从表中选择行

EXPLAIN select * from employee e,department d where e.departmentId = d.id and e.name = 'zz';

image-20210706215416360

6.9 rows列

显示MySQL认为它执行查询时必须检查的行数,这个数字是内嵌循环关联计划里的循环数目,也就是说它不是MySql认为它最终要从表里读取出来的行数,而是MySql为了找到符合查询的每一点上标准的那些行而必须读取的行的平均数

6.10 extra列

这一列包含的是不适合在其他列显示的额外信息,其值有:

  • Using filesort:mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,MySQL中无法利用索引完成排序操作成为“文件排序",极大影响mysql性能,需要尽快优化
explain select name from employee where name = 'zz' order by departmentid;
explain select col1 from t1 where col1 = 'ac' order by col3

image-20210707203846543

排序的时候最好遵循所建索引的顺序与个数否则就可能会出现usering filesort

explain select name from employee where name = 'zz' order by name;
explain select col1 from t1 where col1 = 'ac' order by col2,col3  排序的时候,按照索引顺序进行

image-20210707203942225

  • Using temporary: 使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表,极大影响mysql性能,需要尽快优化,常见于排序order by 和分组查询 group by
explain select col1 from xxx where col1 in ('ac','ab') group by col2

结论:group by一定要遵循所建索引的顺序与个数

  • Using index:表示相应的select操作中使用了覆盖索引(covering index),避免回表操作,效率good
explain select name from employee where name = 'xx';  ###name创建了普通索引
  • Using where:表名使用了where过滤,效率一般

其中using filesort,using temporary,using index最为常见,出现前两种表示是需要优化的地方,出现第三种表示索引效率不错,如果值是null,则表明要进行回表操作

7.mysql调优-索引优化

7.1 单表优化

CREATE TABLE IF NOT EXISTS `article` (
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`author_id` INT(10) UNSIGNED NOT NULL,
`category_id` INT(10) UNSIGNED NOT NULL,
`views` INT(10) UNSIGNED NOT NULL,
`comments` INT(10) UNSIGNED NOT NULL,
`title` VARBINARY(255) NOT NULL,
`content` TEXT NOT NULL
);
##############################################################
INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES
(1, 1, 1, 1, '1', '1'),
(2, 2, 2, 2, '2', '2'),
(1, 1, 3, 3, '3', '3');
SELECT * FROM article;

1.查询 category_id 为1 且 comments 大于 1 的情况下,views 最多的 article_id

EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
结论:很显然,typeALL,即最坏的情况,Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。

image-20210707205924689

2.创建索引

create index idx_article_ccv on article(category_id,comments,views);

再次执行刚才的sql语句

EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1  ORDER BY views DESC LIMIT 1;

结论:type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。但是我们已经建立了索引,为啥没用呢?这是因为按照 BTree 索引的工作原理,先排序 category_id,如果遇到相同的 category_id 则再排序 comments,如果遇到相同的 comments 则再排序 views。当 comments 字段在联合索引里处于中间位置时,因comments > 1 条件是一个范围值(所谓 range),MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效

修改如下:删除第一次建立的索引,第2次新建索引

DROP INDEX idx_article_ccv ON article;
create index idx_article_cv on article(category_id,views);
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;

可以看到type 变为了 ref,另外Extra 中的 Using filesort 也消失了,结果非常理想

7.2 两表优化

CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`)
);
####################################插入数据######################
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

执行sql查询

EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
结论:typeAll

添加索引优化:

ALTER TABLE `book` ADD INDEX Y(`card`)
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;

可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显。这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,**左边一定都有,**所以右边是我们的关键点,一定需要建立索引

注意:左连接,一定要要把索引创建到右表上

注意:左连接,一定要要把索引创建到右表上

注意:左连接,一定要要把索引创建到右表上

说明:

如果把索引创建到左表上,因为左表本来就一定有数据的,所以建了不起作用,大家可以测试如下

DROP INDEX Y ON book;
ALTER TABLE class ADD INDEX X(card);
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
###在class表中只有id和card列,这个时候由于给card建了索引,而且card索引的叶子节点保存了class的id主键,这个时候也用上了card索引,不需要回表操作,所以没有出现ALL的情况,如果class表示多个字段,则不会用到card索引

7.3总结

1、对于单表查询,一定要根据 where 后面的字段建立索引,遇到有 <、>、!= 这样的关系运算符,会使已经建完的索引失效
2、对于双表查询,左外连接只会因为右表的索引而被优化,右外连接只会因为左表的索引而被优化,内连接则因为两个表的索引都可以被优化
3、对于三表查询,则根据是左连接对右边的连接建立索引,根据右连接对左边的连接建立索引

8.索引失效情况(重点)

1、组合索引不遵循最左匹配原则会导致索引失效

2、组合索引的前面索引列使用范围查询(<>,like)会导致后续的索引失效

3、不要在索引上做任何操作(计算,函数,类型转换)

4、is null和is not null无法使用索引

5、尽量少使用or操作符,否则连接时索引会失效

6、字符串不添加引号会导致索引失效

7、两表关联使用的条件字段中字段的长度、编码不一致会导致索引失效

8、like语句中,以%开头的模糊查询,会导致索引失效,如果不想失效,则使用覆盖索引

9、如果mysq|中使用全表扫描比使用索引快,也会导致索引失效

0.素材

create table staffs(
      id int primary key auto_increment,
     name varchar(24) not null default '' comment '姓名',
     age int not null default 0 comment '年龄',
      pos varchar(20) not null default ''comment '职位',
      add_time TIMESTAMP not null default CURRENT_TIMESTAMP comment'入职时间'
     )charset utf8 comment '员工记录表';

 insert into staffs(name,age,pos,add_time)values ('z3',22,'manager',NOW());
 insert into staffs(name,age,pos,add_time)values ('July',23,'dev',NOW());
 insert into staffs(name,age,pos,add_time)values ('2000',23,'dev',NOW());

1.最好全值匹配

建立几个复合索引字段,最好就用上几个字段。且按照顺序来用

如:索引idx_staffs_nameAgePos 建立索引时以 name , age ,pos 的顺序建立的。全值匹配表示按顺序匹配的

create index idx_staffs_nameAgePos  on staffs(name,age,pos)
################################################################
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July';
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25;
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = 'dev';

2.最佳左前缀法则

如果索引了多列,要遵守最左前缀法则,指的是查询从索引的最左前列(即name索引列)开始,不跳过索引中间的列(即age索引列)。

如下:

EXPLAIN SELECT * FROM staffs WHERE name = 'July';
EXPLAIN SELECT * FROM staffs WHERE name = 'July' AND age = 25;
EXPLAIN SELECT * FROM staffs WHERE name = 'July' AND age = 25 AND pos = 'dev';
EXPLAIN SELECT * FROM staffs WHERE age = 25 AND name = 'July' AND pos = 'dev';
EXPLAIN SELECT * FROM staffs WHERE age = 23 AND pos = 'dev';
EXPLAIN SELECT * FROM staffs WHERE pos = 'dev';
EXPLAIN SELECT * FROM staffs WHERE name = 'July' AND pos = 'dev';

结论:

第1-4句都用到索引,第4句为什么也用到了索引呢?and 忽略左右关系。既即使没有没有按顺序,由于优化器的存在,会自动优化,经过试验结论,该表建立了 idx_nameAge 索引和 id 为主键的索引
第5和6句可以发现索引失效了。换句话说:第一个索引一定要用,否则注定失效,第一个好比是火车头,另外两个是车厢,火车头都没了还怎么跑?
第7句只用到了第一个索引
1)当使用覆盖索引的方式时,(select name/age/id from staffs where age=10 (后面没有其他没有索引的字段条件)),即使不是以 name 开头,也会使用 idx_nameAge 索引。
2)除开上述条件,才满足最左前缀法则。

3.不在索引列上做任何操作

不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),否则会导致索引失效而转向全表扫描

mysql> EXPLAIN SELECT * FROM staffs WHERE LEFT(name, 4) = 'July';

从结果可知,用了left左截取函数(对name左截取4个长度的值为July),索引失效

4.范围之后全失效

存储引擎不能使用索引列作为范围条件比较,范围条件右边的列都失效。(范围之后全失效),若中间索引列用到了范围(>、<、like等),则后面的所以全失效

mysql> EXPLAIN SELECT * FROM staffs WHERE name = 'July' AND age = 25 AND pos = 'dev';

将age字段条件从=改成了<,查出的是个范围,所以可发现第三个字段pos索引失效了,因为type类型低了,key_len短了。ref也空了

5.尽量使用覆盖索引

尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),少用select * 查询 ,即不要查询所有列

mysql>EXPLAIN SELECT * FROM staffs WHERE name = 'July'
mysql>EXPLAIN SELECT name FROM staffs WHERE name = 'July';

可以发现,若将替换成索引列的话会用到Using index,直接从索引读,效果更佳,数据量大的时候更明显 *

mysql> EXPLAIN SELECT * FROM staffs WHERE name = 'July' AND age < 25 AND pos = 'dev';

image-20210707212732319

mysql> EXPLAIN SELECT name, age FROM staffs WHERE name = 'July' AND age < 25 AND pos = 'dev';

image-20210707212822999

6.不要使用不等于(!= 或者<>)

mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描

mysql>EXPLAIN SELECT * FROM staffs WHERE name != 'July'  在mysql8里面,用上索引,type级别是range
mysql>EXPLAIN SELECT * FROM staffs WHERE name LIKE '%July';
最终他们的type都是all

7.is not null 也无法使用索引

is not null和is null 也无法使用索引

mysql>EXPLAIN SELECT * FROM staffs WHERE name IS NULL;
mysql>EXPLAIN SELECT * FROM staffs WHERE name IS NOT NULL;

8.like以通配符开头(‘%abc…’)索引失效

like以通配符开头(‘%abc…’)mysql索引失效会变成全表扫描的操作

EXPLAIN SELECT *  FROM staffs WHERE name  like "%abc%"  失效
EXPLAIN SELECT * FROM staffs WHERE name  like "abc%"   有效

面试提问:

问题一:在实际开发中,有时就是要把%写到左右两边,如何也能用上索引,不让索引失效

使用覆盖索引,建的索引和查的字段个数顺序最好完全一致
explain select name,age from staffs where name like "%aa"
explain select id from staffs where name like "%aa"
explain select name from staffs where name like "%aa"
explain select age from staffs where name like "%aa"
explain select id,name from staffs where name like "%aa"
explain select id,name,age from staffs where name like "%aa"
explain select name,age from staffs where name like "%aa"

以上这些都可以在实际上用到索引,id是主键索引,以下两个语句用不到索引,没有给所有列建索引,也没有给add_time列创建索引

explain select * from staffs where name like "%aa"
explain select id,name,age,add_time from staffs where name like "%aa"

9.字符串不加单引号索引失效

EXPLAIN SELECT * FROM staffs WHERE name = 2000;

数据库会把2000隐式的自动的转换为String类型,但是在索引列上做任何操作(计算,函数,(自动or手动)类型转换),会使索引失效

EXPLAIN SELECT * FROM staffs WHERE name = '2000';

10.少用or,用它来连接时会索引失效

EXPLAIN SELECT * FROM staffs WHERE name  like "abc%" or age = 23

9.练习

0.素材

【建表语句】
create table test03(id int primary key not null auto_increment,c1 char(10),c2 char(10),c3 char(10),c4 char(10),c5 char(10));
insert into test03(c1,c2,c3,c4,c5) values('a1','a2','a3','a4','a5');
insert into test03(c1,c2,c3,c4,c5) values('b1','b2','b3','b4','b5');
insert into test03(c1,c2,c3,c4,c5) values('c1','c2','c3','c4','c5');
insert into test03(c1,c2,c3,c4,c5) values('d1','d2','d3','d4','d5');
insert into test03(c1,c2,c3,c4,c5) values('e1','e2','e3','e4','e5');
select * from test03;

1.创建索引

############创建了复合索引idx_test03_c1234 
create index idx_test03_c1234 on test03(c1,c2,c3,c4);
show index from test03;

2.分析以下语句

#################以下四条都用到索引
explain select * from test03 where c1='a1';
explain select * from test03 where c1='a1' and c2='a2';
explain select * from test03 where c1='a1' and c2='a2' and c3='a3';
explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4';
explain select * from test03 where c1=‘a1’ and c2=‘a2’ and c4=‘a4’ and c3=‘a3’; 用到索引,mysql会自动调整顺序
explain select * from test03 where c1=‘a1’ and c2=‘a2’ and c3>‘a3’ and c4=‘a4’; 只用到c1和c2,c3用在排序上
explain select * from test03 where c1=‘a1’ and c2=‘a2’ and c4>‘a4’ and c3=‘a3’ 只用到c1和c2
explain select * from test03 where c1=‘a1’ and c2=‘a2’ and c4=‘a4’ order by c3; c3作用在排序而不是查找
explain select * from test03 where c1=‘a1’ and c2=‘a2’ order by c3; c3作用在排序而不是查找
explain select * from test03 where c1=‘a1’ and c2=‘a2’ order by c4; 出现了filesort,中间c3断了
explain select * from test03 where c1=‘a1’ and c5=‘a5’ order by c2,c3; 只用c1一个字段索引,但是c2、c3用于排序,无filesort
explain select * from test03 where c1=‘a1’ and c5=‘a5’ order by c3,c2; 出现了filesort,我们建的索引是1234,它没有按照顺序来,3 2 颠倒了
explain select * from test03 where c1=‘a1’ and c2=‘a2’ order by c2,c3; 用到c1、c2索引,c3排序
explain select * from test03 where c1=‘a1’ and c2=‘a2’ and c5=‘a5’ order by c2,c3; 用c1、c2两个字段索引,但是c2、c3用于排序,无filesort
explain select c1 from test03 where c1=‘a1’ and c4=‘a4’ group by c3,c2; 出现Using where; Using temporary;

d c2=‘a2’ and c4=‘a4’ order by c3; | c3作用在排序而不是查找 |
| explain select * from test03 where c1=‘a1’ and c2=‘a2’ order by c3; | c3作用在排序而不是查找 |
| explain select * from test03 where c1=‘a1’ and c2=‘a2’ order by c4; | 出现了filesort,中间c3断了 |
| explain select * from test03 where c1=‘a1’ and c5=‘a5’ order by c2,c3; | 只用c1一个字段索引,但是c2、c3用于排序,无filesort |
| explain select * from test03 where c1=‘a1’ and c5=‘a5’ order by c3,c2; | 出现了filesort,我们建的索引是1234,它没有按照顺序来,3 2 颠倒了 |
| explain select * from test03 where c1=‘a1’ and c2=‘a2’ order by c2,c3; | 用到c1、c2索引,c3排序 |
| explain select * from test03 where c1=‘a1’ and c2=‘a2’ and c5=‘a5’ order by c2,c3; | 用c1、c2两个字段索引,但是c2、c3用于排序,无filesort |
| explain select c1 from test03 where c1=‘a1’ and c4=‘a4’ group by c3,c2; | 出现Using where; Using temporary; |

猜你喜欢

转载自blog.csdn.net/lps12345666/article/details/131163627