MySQL索引详解

SQL优化主要就是在优化索引。

索引的弊端:
            1.索引本身很大, 可以存放在内存/硬盘(通常为硬盘)
            2.索引不是所有情况均适用: a.少量数据  b.频繁更新的字段   c.很少使用的字段
            3.索引会降低增删改的效率(增删改查)

优势:1.提高查询效率(降低IO使用率)
           2.降低CPU使用率 (...order by age desc,因为 B树索引 本身就是一个 好排序的结构,因此在排序时  可以直接使用)

索引分类:

       主键索引  :不能重复。id    不能是null
        唯一索引  :不能重复。id    可以是null
        单值索引  :单列, age ;一个表可以多个单值索引,name。
        复合索引  :多个列构成的索引 (相当于 二级目录 )  (name,age)   (a,b,c,d,...,n)

创建索引:

        方式一:create 索引类型  索引名  on 表(字段)

        单值索引:create index  dept_index on  tb(dept);
        唯一索引:create unique index  name_index on tb(name) ;
        复合索引:create index dept_name_index on tb(dept,name);

        方式二:alter table 表名 索引类型  索引名(字段)

        单值索引:alter table tb add index dept_index(dept) ;
        唯一索引:alter table tb add unique index name_index(name);
        复合索引:alter table tb add index dept_name_index(dept,name);

        注意:如果一个字段是primary key,则改字段默认就是主键索引

删除索引:
        drop index 索引名 on 表名 ;
        drop index name_index on tb ;

查询索引:
        show index from 表名 ;
        show index from 表名 \G;

SQL性能分析: 

查询执行计划: explain +SQL语句
                          explain  select  * from tb ;可以模拟SQL优化器执行SQL语句,从而让开发人员知道自己编写的SQL状况

优化方法,官网:https://dev.mysql.com/doc/refman/5.5/en/optimization.html

 id : 标识符                
 select_type :查询类型
 table :输出行表                                                                                                                                                                           partitions:匹配的分区
 type :联接类型
 possible_keys :可能的索引选择
 key  :实际使用的索引
 key_len :所选键的长度     
 ref  :与索引比较的列
 rows :估计要检查的行
 filtered:按表条件过滤的行百分比
 Extra :额外的信息

部分列详细解释

  1. id: id值相同,从上往下 顺序执行,数据小的表 优先查询;                                                                                                            id值不同:id值越大越优先查询;
  2. select_type:

PRIMARY:包含子查询SQL中的 主查询 (最外层)
SUBQUERY:包含子查询SQL中的 子查询 (非最外层)
simple:简单查询(不包含子查询、union)
derived:衍生查询(使用到了临时表)
    a.在from子查询中只有一张表
        explain select  cr.cname  from ( select * from course where tid in (1,2) ) cr                                                                                    b.在from子查询中, 如果有table1 union table2 ,则table1 就是derived,table2就是union
        explain select  cr.cname  from ( select * from course where tid = 1  union select * from course where tid = 2 ) cr ;
union:上例
union result :从 union 临时表检索结果的 select

3.type:索引类型

依次从最优到最差分别为:system>const>eq_ref>ref>range>index>all ,要对type进行优化的前提:有索引 。                                                                        其中:system,const只是理想情况;实际能达到 ref>range ;                                                                                                            system(忽略): 只有一条数据的系统表 ;或衍生表只有一条数据的主查询。

const:仅仅能查到一条数据的SQL ,用于Primary key 或unique索引  (类型与索引类型有关)
explain select tid from test01 where tid =1 ;
alter table test01 drop primary key ;
create index test01_index on test01(tid) ;

eq_ref:唯一性索引:对于每个索引键的查询,返回匹配唯一行数据(有且只有1个,不能多 、不能0)
select ... from ..where name = ... .常见于唯一索引和主键索引。

 alter table teacherCard add constraint pk_tcid primary key(tcid);
alter table teacher add constraint uk_tcid unique index(tcid) ;


explain select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid ;

以上SQL,用到的索引是 t.tcid,即teacher表中的tcid字段;
如果teacher表的数据个数和连接查询的数据个数一致(都是3条数据),则有可能满足eq_ref级别;否则无法满足。


ref:非唯一性索引,对于每个索引键的查询,返回匹配的所有行(0行或者多行)
准备数据:
 insert into teacher values(4,'tz',4) ;
 insert into teacherCard values(4,'tz222');

测试:
alter table teacher add index index_name (tname) ;
explain select * from teacher  where tname = 'tz';


range:检索指定范围的行 ,where后面是一个范围查询(between  ,> < >=,     特殊:in有时候会失效 ,从而转为无索引all)
alter table teacher add index tid_index (tid) ;
explain select t.* from teacher t where t.tid in (1,2) ;
explain select t.* from teacher t where t.tid <3 ;

index:查询全部索引中数据
explain select tid from teacher ; --tid 是索引, 只需要扫描索引表,不需要所有表中的所有数据


all:查询全部表中的数据
explain select cid from course ;  --cid不是索引,需要全表扫描,即需要所有表中的所有数据


system/const: 结果只有一条数据
eq_ref:结果多条;但是每条数据是唯一的 ;
ref:结果多条;但是每条数据是是0或多条 ;

4.rows: 被索引优化查询的数据个数 (实际通过索引而查询到的数据个数)
    explain select * from course c,teacher t  where c.tid = t.tid
    and t.tname = 'tz' ;

5.key_len:这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列

key_len计算规则如下:
字符串
char(n):n字节长度
varchar(n):2字节存储字符串长度,如果是utf-8,则长度 3n + 2
数值类型
tinyint:1字节
smallint:2字节
int:4字节
bigint:8字节  
时间类型 
date:3字节
timestamp:4字节
datetime:8字节
如果字段允许为 NULL,需要1字节记录是否为 NULL
索引最大长度是768字节,当字符串过长时,mysql会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引。
 

6. Extra:
    (i).using filesort : 性能消耗大;需要“额外”的一次排序(查询)  。常见于 order by 语句中。
        explain select * from test02 where a1 ='' order by a2 ; --using filesort
       小结:对於单索引, 如果排序和查找是同一个字段,则不会出现using filesort;如果排序和查找不是同一个字段,则会出现         using filesort;
       避免: where哪些字段,就order by哪些字段


复合索引:不能跨列(最佳左前缀)
drop index idx_a1 on test02;
drop index idx_a2 on test02;
drop index idx_a3 on test02;

alter table test02 add index idx_a1_a2_a3 (a1,a2,a3) ;
explain select *from test02 where a1='' order by a3 ;  --using filesort
explain select *from test02 where a2='' order by a3 ; --using filesort
explain select *from test02 where a1='' order by a2 ;
explain select *from test02 where a2='' order by a1 ; --using filesort
    小结:避免: where和order by 按照复合索引的顺序使用,不要跨列或无序使用。

    (ii). using temporary:性能损耗大 ,用到了临时表。一般出现在group by 语句中。
    explain select a1 from test02 where a1 in ('1','2','3') group by a1 ;
    explain select a1 from test02 where a1 in ('1','2','3') group by a2 ; --using temporary
    避免:查询那些列,就根据那些列 group by .

    (iii). using index :性能提升; 索引覆盖(覆盖索引)。原因:不读取原文件,只从索引文件中获取数据 (不需要回表查询)
        只要使用到的列 全部都在索引中,就是索引覆盖using index

    例如:test02表中有一个复合索引(a1,a2,a3)
        explain select a1,a2 from test02 where a1='' or a2= '' ; --using index   
        
        drop index idx_a1_a2_a3 on test02;

        alter table test02 add index idx_a1_a2(a1,a2) ;
        explain select a1,a3 from test02 where a1='' or a3= '' ;

        
        如果用到了索引覆盖(using index时),会对 possible_keys和key造成影响:
        a.如果没有where,则索引只出现在key中;
        b.如果有where,则索引 出现在key和possible_keys中。

        explain select a1,a2 from test02 where a1='' or a2= '' ;
        explain select a1,a2 from test02  ;

    (iii).using where (需要回表查询)
        假设age是索引列
        但查询语句select age,name from ...where age =...,此语句中必须回原表查Name,因此会显示using where.
        
    explain select a1,a3 from test02 where a3 = '' ; --a3需要回原表查询
    

    (iv). impossible where :where子句永远为false
        explain select * from test02 where a1='x' and a1='y'  ;

索引数据结构:

  1. 二叉树
  2. 红黑树
  3. Hash表
  4. B-Tree

根据这个链接可以模仿这些如何进行增加节点,转换的:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

为什么索引会采用B+Tree数据结果呢?以下分析:

二叉树,在某种情况下回会退化成链表结构:

红黑树会进行自适应,进行平衡,但是数据量大的时候也会出现问题,深度过高,会增加I/O次数:

hash表:hash查询速度也快,但是进行范围查找就不行。

B-Tree:横向扩展,高度低。(横向查找在同一页中,不需要更多的磁盘I/O,内存中的遍历速度可以忽略不计)

B+Tree:有指针,能很好的支撑范围查找。(叶子节点中其实是双向指针)

索引最佳实践
使用的表
CREATE TABLE `employees` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
  `position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
  `hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
  PRIMARY KEY (`id`),
  KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='员工记录表';

INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei', 23,'dev',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());

最佳实践
1. 全值匹配
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei';
 

EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22;
 

EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
 

2.最佳左前缀法则
 如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
EXPLAIN SELECT * FROM employees WHERE age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE position = 'manager';
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
 

3.不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei';
 

4.存储引擎不能使用索引中范围条件右边的列
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager';
 

5.尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少select *语句
EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
 
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
 

6.mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
EXPLAIN SELECT * FROM employees WHERE name != 'LiLei'
 

7.is null,is not null 也无法使用索引
EXPLAIN SELECT * FROM employees WHERE name is null
 

8.like以通配符开头('$abc...')mysql索引失效会变成全表扫描操作
EXPLAIN SELECT * FROM employees WHERE name like '%Lei'
 
EXPLAIN SELECT * FROM employees WHERE name like 'Lei%'
 

问题:解决like'%字符串%'索引不被使用的方法?
a)使用覆盖索引,查询字段必须是建立覆盖索引字段
EXPLAIN SELECT name,age,position FROM employees WHERE name like '%Lei%';
 
b)当覆盖索引指向的字段是varchar(380)及380以上的字段时,覆盖索引会失效!

9.字符串不加单引号索引失效
EXPLAIN SELECT * FROM employees WHERE name = '1000';
EXPLAIN SELECT * FROM employees WHERE name = 1000;
 

10.少用or,用它连接时很多情况下索引会失效
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' or name = 'HanMeimei';

like KK%相当于=常量,%KK和%KK% 相当于范围
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章