mysql 索引

 

本文将对Mysql索引进行总结。

1 索引概述

  • 索引是帮助MySql高效获取数据的数据结构。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

  • 在数据之外,数据库系统还维护着满足特定查找算法的数据结构,包括B+树或者Hash表。由于存储引擎表示的是数据在磁盘上面的不同的组织形式,所以索引底层采用哪种数据结构是跟数据库的存储引擎相关的。如果是MyIsam或者是InnoDB存储引擎,那么对应的底层的数据结构为B+树,如果是Memory存储引擎,那么对应的底层的数据结构为Hash表。采用B+树的最根本的原因是由于二叉树的树太高,树太高则直接影响到磁盘IO的次数,影响数据查询的效率,采用B+树的数据结构,可以在某个数据节点里面尽可能多的存储数据,使树的高度尽量的变低,提高效率。日常开发过程中,遇到的比较多的可能就是聚簇索引和联合索引,里面又涉及到了覆盖索引,最左匹配,回表,索引下推等各方面的知识点,在编写SQL语句的时候,我们就可以利用这些点来进行优化,提高数据的查询效率。

  • 一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。索引是数据库中用来提高性能的最常用的工具。

  • 索引的优点与缺点

    优点:

    • 类似于书籍的目录索引,提高数据检索的效率,降低数据库的IO成本。

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

    • 索引大大减小了服务器需要扫描的数据量,从而大大加快数据的检索速度,这也是创建索引的最主要的原因。

    • 索引可以帮助服务器避免排序和创建临时表

    • 索引可以将随机IO变成顺序IO

    • 索引对于InnoDB(对索引支持行级锁)非常重要,因为它可以让查询锁更少的元组,提高了表访问并发性

    • 关于InnoDB、索引和锁:InnoDB在二级索引上使用共享锁(读锁),但访问主键索引需要排他锁(写锁)

    • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

    • 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。

    • 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

    • 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

    缺点:

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

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

  • 适合创建索引的列

    • 在经常需要搜索的列上,可以加快搜索的速度

    • 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构

    • 在经常用在连接(JOIN)的列上,这些列主要是一外键,可以加快连接的速度

    • 在经常需要根据范围(<,<=,=,>,>=,BETWEEN,IN)进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的

    • 在经常需要排序(order by)的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;

    • 在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。

  • 不适合创建索引的列

    • 对于那些在查询中很少使用或者参考的列不应该创建索引。

      若列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。

    • 对于那些只有很少数据值或者重复值多的列也不应该增加索引。

      这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。

    • 对于那些定义为text, image和bit数据类型的列不应该增加索引。

      这些列的数据量要么相当大,要么取值很少。

    • 当该列修改性能要求远远高于检索性能时,不应该创建索引。(修改性能和检索性能是互相矛盾的)

2 索引存储结构

  • B-Tree

    磁盘 IO 特点:从磁盘读取1B 数据和 1KB 数据所消耗的时间是基本一样的(空间局部性与时间局部性决定),根据这个思路,可以在一个树节点上尽可能多地存储数据,一次磁盘 IO 就尽可能多的加载数据到内存,影响数据查询时间的是树的高度,高度越高,比较的次数越多,尽量把树的高度降低,这就是B树的设计原理了,下图是3阶B树:

    B树的特征:

    • 关键字集合分布在整颗树中;

    • 任何一个关键字出现且只出现在一个结点中;

    • 搜索有可能在非叶子结点结束;

    • 其搜索性能等价于在关键字全集内做一次二分查找;

    • 自动层次控制;

  • B+ Tree

    一个树节点我们应该尽可能的包含更多的子节点,但又不能超过一个磁盘页(16kb)的大小。发现B树的节点中还包含了一些关键字信息data,这个data也占据着一定的数据量,如果把data去掉,这样就又能多加很多子节点了。这也就是B+树的核心思想。

    B+树的特征:

    • 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;

    • 不可能在非叶子结点命中;

    • 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;

    • 每一个叶子节点都包含指向下一个叶子节点的指针,从而方便叶子节点的范围遍历。

    • 更适合文件索引系统;

    B+树和B树有什么不同:

    • B+树非叶子节点不存储数据的,仅存储键值(索引地址),而B树节点中不仅存储键值,也会存储数据。B+树之所以这么做是因为在数据库中页的大小是固定的,innodb中页的默认大小是16KB。如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的IO次数会再次减少,数据查询的效率也会更快 。

    • B+树索引的所有数据均存储在叶子节点,且数据是按照顺序排列的。B+树使得范围查找,排序查找,分组查找以及去重查找变得简单高效

    • B+树各个页之间是通过双向链表连接,叶子节点中的数据是通过单向链表连接的。我们通过双向链表和单向链表连接的方式可以找到表中所有的数据。

  • HASH

    哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。

    Hash索引仅仅能满足"=","IN""<=>"查询,不能使用范围查询。也不支持任何范围查询,例如WHERE price > 100

    由于Hash索引比较的是进行Hash运算之后的Hash值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的Hash算法处理之后的Hash值的大小关系,并不能保证和Hash运算前完全一样。

    相比于B-Tree索引而言,哈希索引有不少的局限性:

    • 哈希索引不支持排序
    • 哈希索引不支持部分列索引查找
    • 哈希索引只支持等值查询,无法提供范围查询功能
    • 哈希索引的查找效率是非常高的,大多数时候都能在O(1)的时间内找到记录,除非哈希冲突很高。

3 索引分类

  • 按存储结构分:

    • B-Tree
    • B+Tree
    • Hash索引
  • 按逻辑分

    逻辑上可以按功能划分以及按组成索引的列数划分:

    1 按功能划分

    • 主键索引:一张表只能有一个主键索引,不允许重复、不允许为 NULL;

        ALTER TABLE TableName ADD PRIMARY KEY(column_list);    
      
    • 唯一索引:数据列不允许重复,允许为 NULL 值,一张表可有多个唯一索引,索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。

        CREATE UNIQUE INDEX IndexName ON `TableName`(`字段名`(length));
        --或者
        ALTER TABLE TableName ADD UNIQUE (column_list); 
      
    • 普通索引:一张表可以创建多个普通索引,一个普通索引可以包含多个字段,允许数据重复,允许 NULL 值插入;

        CREATE INDEX IndexName ON `TableName`(`字段名`(length));
        --或者
        ALTER TABLE TableName ADD INDEX IndexName(`字段名`(length));
      
    • 全文索引:它查找的是文本中的关键词,主要用于全文检索。

    2 按列数划分

    • 单例索引:一个索引只包含一个列,一个表可以有多个单例索引。

    • 组合索引:一个组合索引包含两个或两个以上的列。查询的时候遵循 mysql 组合索引的 “最左前缀”原则,即使用 where 时条件要按照建立索引的时候字段的排列方式放置索引才会生效。

  • 按物理存储划分

    分为聚簇索引和非聚簇索引(有时也称辅助索引或二级索引)。

    • 聚簇索引(clustered index)不是单独的一种索引类型,而是一种数据存储方式。这种存储方式是依靠B+树来实现的,根据表的主键构造一棵B+树且B+树叶子节点存放的都是表的行记录数据时,方可称该主键索引为聚簇索引。聚簇索引也可理解为将数据存储与索引放到了一块,找到索引也就找到了数据。聚簇是为了提高某个属性(或属性组)的查询速度,把这个或这些属性(称为聚簇码)上具有相同值的元组集中存放在连续的物理块。

    • 非聚簇索引:数据和索引是分开的,B+树叶子节点存放的不是数据表的行记录。

    虽然InnoDB和MyISAM存储引擎都默认使用B+树结构存储索引,但是只有InnoDB的主键索引才是聚簇索引,InnoDB中的辅助索引以及MyISAM使用的都是非聚簇索引。每张表最多只能拥有一个聚簇索引。

    聚簇索引优缺点:

    优点:

    • 数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快

    • 聚簇索引对于主键的排序查找和范围查找速度非常快

    缺点:

    • 插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键(主键列不要选没有意义的自增列,选经常查询的条件列才好,不然无法体现其主键索引性能)

    • 更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。

    • 二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。

4 索引的操作

  • 创建索引

    索引名称 index_name 是可以省略的,省略后,索引的名称和索引列名相同。

      -- 创建普通索引 
      CREATE INDEX index_name ON table_name(col_name);
    
      -- 创建唯一索引
      CREATE UNIQUE INDEX index_name ON table_name(col_name);
    
      -- 创建普通组合索引
      CREATE INDEX index_name ON table_name(col_name_1,col_name_2);
    
      -- 创建唯一组合索引
      CREATE UNIQUE INDEX index_name ON table_name(col_name_1,col_name_2);
    

    修改表结构创建索引

      ALTER TABLE table_name ADD INDEX index_name(col_name);
    

    创建表时直接指定索引

      CREATE TABLE table_name (
      ID INT NOT NULL,
      col_name VARCHAR (16) NOT NULL,
      INDEX index_name (col_name)
      );
    
  • 删除索引

      -- 直接删除索引
      DROP INDEX index_name ON table_name;
    
      -- 修改表结构删除索引
      ALTER TABLE table_name DROP INDEX index_name;
    
  • 其它相关命令

      -- 查看表结构
      desc table_name;
    
      -- 查看生成表的SQL
      show create table table_name;
    
      -- 查看索引信息(包括索引结构等)
      show index from  table_name;
    
      -- 查看SQL执行时间(精确到小数点后8位)
      set profiling = 1;
      SQL...
      show profiles;