• MySQL【十一】创建索引


    MySQL创建索引

     

    创建索引

    复制代码
    #方法一:创建表时
          CREATE TABLE 表名 (
                    字段名1  数据类型 [完整性约束条件…],
                    字段名2  数据类型 [完整性约束条件…],
                    [UNIQUE | FULLTEXT | SPATIAL ]   INDEX | KEY
                    [索引名]  (字段名[(长度)]  [ASC |DESC]) 
                    );
    
    
    #方法二:CREATE在已存在的表上创建索引
            CREATE  [UNIQUE | FULLTEXT | SPATIAL ]  INDEX  索引名 
                         ON 表名 (字段名[(长度)]  [ASC |DESC]) ;
    
    
    #方法三:ALTER TABLE在已存在的表上创建索引
            ALTER TABLE 表名 ADD  [UNIQUE | FULLTEXT | SPATIAL ] INDEX
                                 索引名 (字段名[(长度)]  [ASC |DESC]) ;
                                 
    #删除索引:DROP INDEX 索引名 ON 表名字;
    复制代码
    #方式一
    create table t1(
        id int,
        name char,
        age int,
        sex enum('male','female'),
        unique key uni_id(id),
        index ix_name(name) #index没有key
    );
    create table t1(
        id int,
        name char,
        age int,
        sex enum('male','female'),
        unique key uni_id(id),
        index(name) #index没有key
    );
    
    
    #方式二
    create index ix_age on t1(age);
    
    
    #方式三
    alter table t1 add index ix_sex(sex);
    alter table t1 add index(sex);
    
    #查看
    mysql> show create table t1;
    | t1    | CREATE TABLE `t1` (
      `id` int(11) DEFAULT NULL,
      `name` char(1) DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      `sex` enum('male','female') DEFAULT NULL,
      UNIQUE KEY `uni_id` (`id`),
      KEY `ix_name` (`name`),
      KEY `ix_age` (`age`),
      KEY `ix_sex` (`sex`)
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1
    
    例子
    例子

    测试索引

    数据准备

    #1. 准备表
    create table s1(
    id int,
    name varchar(20),
    gender char(6),
    email varchar(50)
    );
    
    #2. 创建存储过程,实现批量插入记录
    delimiter $$ #声明存储过程的结束符号为$$
    create procedure auto_insert1()
    BEGIN
        declare i int default 1;
        while(i<3000000)do
            insert into s1 values(i,'eva','female',concat('eva',i,'@oldboy'));
            set i=i+1;
        end while;
    END$$ #$$结束
    delimiter ; #重新声明分号为结束符号
    
    #3. 查看存储过程
    show create procedure auto_insert1G 
    
    #4. 调用存储过程
    call auto_insert1();
    View Code

    在没有索引的前提下测试查询速度

    #无索引:mysql根本就不知道到底是否存在id等于333333333的记录,只能把数据表从头到尾扫描一遍,此时有多少个磁盘块就需要进行多少IO操作,所以查询速度很慢
    mysql> select * from s1 where id=333333333;
    Empty set (0.33 sec)

    在表中已经存在大量数据的前提下,为某个字段段建立索引,建立速度会很慢

    在索引建立完毕后,以该字段为查询条件时,查询速度提升明显

    PS:

    1. mysql先去索引表里根据b+树的搜索原理很快搜索到id等于333333333的记录不存在,IO大大降低,因而速度明显提升

    2. 我们可以去mysql的data目录下找到该表,可以看到占用的硬盘空间多了

    3. 需要注意,如下图

    总结

    复制代码
    #1. 一定是为搜索条件的字段创建索引,比如select * from s1 where id = 333;就需要为id加上索引
    
    #2. 在表中已经有大量数据的情况下,建索引会很慢,且占用硬盘空间,建完后查询速度加快
    比如create index idx on s1(id);会扫描表中所有的数据,然后以id为数据项,创建索引结构,存放于硬盘的表中。
    建完以后,再查询就会很快了。
    
    #3. 需要注意的是:innodb表的索引会存放于s1.ibd文件中,而myisam表的索引则会有单独的索引文件table1.MYI
    
    MySAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在innodb中,表数据文件本身就是按照B+Tree(BTree即Balance True)组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此innodb表数据文件本身就是主索引。
    因为inndob的数据文件要按照主键聚集,所以innodb要求表必须要有主键(Myisam可以没有),如果没有显式定义,则mysql系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则mysql会自动为innodb表生成一个隐含字段作为主键,这字段的长度为6个字节,类型为长整型.
    复制代码

    如果才能正确命中索引

    索引未命中

    并不是说我们创建了索引就一定会加快查询速度,若想利用索引达到预想的提高查询速度的效果,我们在添加索引时,必须遵循以下问题

    范围问题,或者说条件不明确

    条件中出现这些符号或关键字:>、>=、<、<=、!= 、between...and...、like、

    大于号、小于号

    不等于!=

    between ...and...

    like

    区分度问题

    尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录

    mysql> desc s1;
    +--------+-------------+------+-----+---------+-------+
    | Field  | Type        | Null | Key | Default | Extra |
    +--------+-------------+------+-----+---------+-------+
    | id     | int(11)     | YES  | MUL | NULL    |       |
    | name   | varchar(20) | YES  |     | NULL    |       |
    | gender | char(5)     | YES  |     | NULL    |       |
    | email  | varchar(50) | YES  | MUL | NULL    |       |
    +--------+-------------+------+-----+---------+-------+
    rows in set (0.00 sec)
    
    mysql> drop index a on s1;
    Query OK, 0 rows affected (0.20 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> drop index d on s1;
    Query OK, 0 rows affected (0.18 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> desc s1;
    +--------+-------------+------+-----+---------+-------+
    | Field  | Type        | Null | Key | Default | Extra |
    +--------+-------------+------+-----+---------+-------+
    | id     | int(11)     | YES  |     | NULL    |       |
    | name   | varchar(20) | YES  |     | NULL    |       |
    | gender | char(5)     | YES  |     | NULL    |       |
    | email  | varchar(50) | YES  |     | NULL    |       |
    +--------+-------------+------+-----+---------+-------+
    rows in set (0.00 sec)
    
    先把表中的索引都删除,让我们专心研究区分度的问题
    先把表中的索引都删除,让我们专心研究区分度的问题

    我们编写存储过程为表s1批量添加记录,name字段的值均为egon,也就是说name这个字段的区分度很低(gender字段也是一样的,我们稍后再搭理它)
    
    回忆b+树的结构,查询的速度与树的高度成反比,要想将树的高低控制的很低,需要保证:在某一层内数据项均是按照从左到右,从小到大的顺序依次排开,即左1<左2<左3<...
    
    而对于区分度低的字段,无法找到大小关系,因为值都是相等的,毫无疑问,还想要用b+树存放这些等值的数据,只能增加树的高度,字段的区分度越低,则树的高度越高。极端的情况,索引字段的值都一样,那么b+树几乎成了一根棍。本例中就是这种极端的情况,name字段所有的值均为'egon'
    
    #现在我们得出一个结论:为区分度低的字段建立索引,索引树的高度会很高,然而这具体会带来什么影响呢???
    
    #1:如果条件是name='xxxx',那么肯定是可以第一时间判断出'xxxx'是不在索引树中的(因为树中所有的值均为'egon’),所以查询速度很快
    
    #2:如果条件正好是name='egon',查询时,我们永远无
    
    分析原因
    分析原因

    索引列不能在条件中参与计算

    索引列不能在条件中参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’)

     

     and/or

    复制代码
    #1、and与or的逻辑
        条件1 and 条件2:所有条件都成立才算成立,但凡要有一个条件不成立则最终结果不成立
        条件1 or 条件2:只要有一个条件成立则最终结果就成立
    
    #2、and的工作原理
        条件:
            a = 10 and b = 'xxx' and c > 3 and d =4
        索引:
            制作联合索引(d,a,b,c)
        工作原理:
            对于连续多个and:mysql会按照联合索引,从左到右的顺序找一个区分度高的索引字段(这样便可以快速锁定很小的范围),加速查询,即按照d—>a->b->c的顺序
    
    #3、or的工作原理
        条件:
            a = 10 or b = 'xxx' or c > 3 or d =4
        索引:
            制作联合索引(d,a,b,c)
            
        工作原理:
            对于连续多个or:mysql会按照条件的顺序,从左到右依次判断,即a->b->c->d
    复制代码

    在左边条件成立但是索引字段的区分度低的情况下(name与gender均属于这种情况),会依次往右找到一个区分度高的索引字段,加速查询

    经过分析,在条件为name='egon' and gender='male' and id>333 and email='xxx'的情况下,我们完全没必要为前三个条件的字段加索引,因为只能用上email字段的索引,前三个字段的索引反而会降低我们的查询效率

    最左前缀匹配原则

    非常重要的原则,对于组合索引mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配(指的是范围大了,有索引速度也慢),比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

    其他情况

    复制代码
    使用函数
        select * from tb1 where reverse(email) = 'egon';
                
    - 类型不一致
        如果列是字符串类型,传入条件是必须用引号引起来,不然...
        select * from tb1 where email = 999;
        
    #排序条件为索引,则select字段必须也是索引字段,否则无法命中
    - order by
        select name from s1 order by email desc;
        当根据索引排序时候,select查询的字段如果不是索引,则速度仍然很慢
        select email from s1 order by email desc;
        特别的:如果对主键排序,则还是速度很快:
            select * from tb1 order by nid desc;
     
    - 组合索引最左前缀
        如果组合索引为:(name,email)
        name and email       -- 命中索引
        name                 -- 命中索引
        email                -- 未命中索引
    
    
    - count(1)或count(列)代替count(*)在mysql中没有差别了
    
    - create index xxxx  on tb(title(19)) #text类型,必须制定长度
    复制代码

    其他注意事项

    复制代码
    - 避免使用select *
    - 使用count(*)
    - 创建表时尽量使用 char 代替 varchar
    - 表的字段顺序固定长度的字段优先
    - 组合索引代替多个单列索引(由于mysql中每次只能使用一个索引,所以经常使用多个条件查询时更适合使用组合索引)
    - 尽量使用短索引
    - 使用连接(JOIN)来代替子查询(Sub-Queries)
    - 连表时注意条件类型需一致
    - 索引散列值(重复少)不适合建索引,例:性别不适合
    复制代码

    联合索引

    联合索引

    联合索引是指对表上的多个列合起来做一个索引。联合索引的创建方法与单个索引的创建方法一样,不同之处仅在于有多个索引列,如:

    复制代码
     
    mysql> create table t(
        -> a int,
        -> b int,
        -> primary key(a),
        -> key idx_a_b(a,b)
        -> );
    Query OK, 0 rows affected (0.11 sec)
     
    复制代码

    覆盖索引

    InnoDB存储引擎支持覆盖索引(covering index,或称索引覆盖),即从辅助索引中就可以得到查询记录,而不需要查询聚集索引中的记录。

    覆盖索引 查一个数据不需要回表
        select name from 表 where age = 20   不是覆盖索引
        select age from 表 where age =20  是覆盖索引
        select count(age) from 表 where age =20  是覆盖索引

    合并索引

    复制代码
     
    当我们为单独的一列创建索引的时候
        如果条件是这一列,且使用正确就可以命中索引
    当我们为两列分别创建单独的索引的时候
        如果这两列都是条件,那么可能只能命中期中一个条件
        如果这两列都是条件,那么可能会命中两个索引  - 合并索引
    我们为多列直接创建联合所以
        条件命中联合索引
     
    复制代码

    索引总结:

    复制代码
     
    1.条件一定是建立了索引的字段,如果条件使用的字段根本就没有创建索引,那么索引不生效
    2.如果条件是一个范围,随着范围的值逐渐增大,那么索引能发挥的作用也越小
    3.如果使用like进行模糊查询,那么使用a%的形式能命中索引,%a形式不能命中索引
    4.尽量选择区分度高的字段作为索引列
    5.索引列不能在条件中参与计算,也不能使用函数
    6.在多个条件以and相连的时候,会优点选择区分度高的索引列来进行查询
      在多个条件以or相连的时候,就是从左到右依次判断
    7.制作联合索引
        1.最左前缀原则 a,b,c,d 条件是a的能命中索引,条件是a,b能命中索引,a,b,c能命中,a,c.... 只要没有a就不能命中索引
            如果在联合查询中,总是涉及到同一个字段,那么就在建立联合索引的时候将这个字段放在最左侧
        2.联合索引 如果按照定义顺序,从左到右遇到的第一个在条件中以范围为条件的字段,索引失效
            尽量将带着范围查询的字段,定义在联合索引的最后面
        drop index
        如果我们查询的条件总是多个列合在一起查,那么就建立联合索引
            create index ind_mix on s1(id,email)
    
            select * from s1 where id = 1000000    命中索引
            select * from s1 where email = 'eva1000000@oldboy'  未命中索引
            但凡是创建了联合索引,那么在查询的时候,再创建顺序中从左到右的第一列必须出现在条件中
            select count(*) from s1 where id = 1000000 and email = 'eva10%';  命中索引
    
            select count(*) from s1 where id = 1000000 and email like 'eva10%'; 可以命中索引
            范围 :
            select * from s1 where id >3000 and email = 'eva300000@oldboy';  不能命中索引
    8.条件中涉及的字段的值必须和定义表中字段的数据类型一致,否则不能命中索引
     
    复制代码

    执行计划

    执行计划
    看看mysql准备怎么执行这条语句 可以看到是否命中索引,计划能命中哪些,实际命中了哪些,执行的顺序,是否发生了索引合并,覆盖索引
    explain select * from s1;
  • 相关阅读:
    第4月第1天 makefile automake
    第3月30天 UIImage imageWithContentsOfFile卡顿 Can't add self as subview MPMoviePlayerControlle rcrash
    第3月第27天 uitableviewcell复用
    learning uboot fstype command
    learning uboot part command
    linux command dialog
    linux command curl and sha256sum implement download verification package
    learning shell script prompt to run with superuser privileges (4)
    learning shell get script absolute path (3)
    learning shell args handing key=value example (2)
  • 原文地址:https://www.cnblogs.com/youxiu123/p/11493052.html
Copyright © 2020-2023  润新知