索引基础
什么是索引?
官方解释:索引(Index)是帮助MySQL高效获取数据的数据结构。
通俗理解:索引是一种特殊的文件(InnoDB 数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。
为什么使用数据索引能提高效率?
- 数据索引的存储是有序的
- 在有序的情况下,通过索引查询一个数据是无需遍历索引记录的
- 极端情况下,数据索引的查询效率为二分法查询效率,趋近于 log2(N)
索引的优缺点
索引不是万能的,索引可以加快数据检索操作,但会使数据修改操作变慢。
索引的优点:
- 减少服务器需要扫描的数据量
- 避免排序和临时表
- 将随机 I/O 变为 顺序 I/O
索引的缺点:
- 虽然索引大大提高了查询速度,同时却会降低更新表的速度
因为更新表时,MySQL不仅要刷新数据,还要刷新索引文件 - 建立索引会占用磁盘空间的索引文件
一般这个问题不太严重,但如果在一个大表上创建了多种组合索引,索引文件的会膨胀很快 - 如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果
对于非常小的表,大部分情况下简单的全表扫描更高效
MySQL索引都有哪些分类?
按数据结构分类可分为:B-Tree索引、Hash索引、空间数据索引、全文索引。
按物理存储分类可分为:聚簇索引、非聚簇索引(也叫二级索引、辅助索引)。
按字段特性分类可分为:主键索引、普通索引、前缀索引。
按字段个数分类可分为:单列索引、联合索引(也叫复合索引、组合索引)。
1、按数据结构分类
B-Tree索引
B-Tree通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同,很适合查找范围数据。
- 所有键值分布在整颗树中(索引值和具体data都在每个节点里)
- 任何一个关键字出现且只出现在一个结点中
- 搜索有可能在非叶子结点结束(最好情况O(1)就能找到数据)
- 在关键字全集内做一次查找,性能逼近二分查找
InnoDB使用的是B+Tree:
B+Tree的每一个叶子节点都包含指向下一个叶子节点的指针,从而方便叶子节点的范围遍历。
- 叶子节点保存了完整的索引和数据,而非叶子节点只保存索引值,因此它的查询时间固定为 log(n)
- 叶子节点中有指向下一个叶子节点的指针,叶子节点类似于一个单链表
- 因为叶子节点保存了完整的数据以及有指针作为连接,B+树可以增加了区间访问性,提高了范围查询,而B树的范围查询相对较差
- B+树更适合外部存储,因为它的非叶子节点不存储数据,只保存索引
为什么说B+比B树更适合实际应用中操作系统的文件索引和数据库索引?
- B+的磁盘读写代价更低
- B+的内部结点并没有指向关键字具体信息的指针,因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了
- B+tree的查询效率更加稳定。
- 由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当
组合索引
组合索引是两个或更多个列上的索引。
- 由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当
对于组合索引,MySQL支持从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。
组合索引的结构与电话簿类似,人名由姓和名构成,电话簿首先按姓氏对进行排序,然后按名字对有相同姓氏的人进行排序。如果您知道姓,电话簿将非常有用;如果您知道姓和名,电话簿则更为有用,但如果您只知道名不知道姓,电话簿将没有用处。
假设有如下一个表:
CREATE TABLE People (
last_name varchar(50) not null,
first_name varchar(50) not null,
dob date not null,
gender enum(\'m\', \'f\') not null,
key(last_name, first_name, dob)
);
其组合索引包含表中每一行的last_name、first_name和dob列。其结构如下:
按索引的最左边前缀(leftmost prefix of the index)来进行查询:
- 查询必须从索引的最左边的列开始,否则无法使用索引
- 例如不能直接利用索引查找在某一天出生的人
- 不能跳过某一索引列
- 例如不能利用索引查找last name为Smith且出生于某一天的人
- 存储引擎不能使用索引中范围条件右边的列
- 查询语句为WHERE last_name="Smith" AND first_name LIKE \'J%\' AND dob=\'1976-12-23\',该查询只会使用索引中的前两列,因为LIKE是范围查询
哈希索引
基于哈希表实现,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可。
哈希索引的优势:
- 等值查询,Hash 索引的查询效率要远高于 B-Tree 索引
- 前提是:没有大量重复键值,如果大量重复键值时,哈希索引的效率很低,因为存在所谓的哈希碰撞问题
哈希索引不适用的情况:
- 不支持范围查询
- 不支持索引完成排序
- 不支持联合索引的最左前缀匹配规则
空间数据索引(R-Tree)
在涉及LBS的服务开发过程中,经常需要存储地理空间的位置并进行一定计算。
MyISAM支持空间索引,主要用于地理空间数据类型。
空间数据索引和 B-Treee 索引不同,这类索引无须前缀查询。空间索引会从所有维度来索引数据。查询时,可以有效地使用任意维度来组合查询。
全文索引
通过数值比较、范围过滤等就可以完成绝大多数我们需要的查询,但是,如果希望通过关键字的匹配来进行查询过滤,那么就需要基于相似度的查询,而不是原来的精确数值比较。全文索引就是为这种场景设计的。
全文索引查找的是文本中的关键词,更类似于搜索引擎所做的事情,而不是简单的 WHERE 条件匹配。
全文索引通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题。
倒排索引(Inverted index):也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。
2、按物理存储分类
MySQL索引按叶子节点存储的是否为完整表数据分为:聚簇索引、非聚簇索引(二级索引、辅助索引)。
聚簇索引(cluster index)
聚簇索引(Clustered indexes)是指索引和数据存储在一块( 都存储在同一个 B-Tree 中)。
聚簇索引的每个叶子节点存储了一行完整的表数据,叶子节点间按id列递增连接,可以方便地进行顺序检索。
InnoDB表要求必须有聚簇索引,默认在主键字段上建立聚簇索引,在没有主键字段的情况下,表的第一个非空的唯一索引将被建立为聚簇索引,在前两者都没有的情况下,InnoDB将自动生成一个隐式的自增id列,并在此列上建立聚簇索引。
MyISAM为存储引擎的表不存在聚簇索引
MyISAM表中的主键索引和非主键索引的结构是一样的,索引的叶子节点不存储表数据,存放的是表数据的地址。所以,MyISAM表可以没有主键。
非聚簇索引(二级索引)
二级索引的数据和存储数据是分离的,它的叶子节点并不存储一行完整的表数据,而是存储了聚簇索引所在列的值。
回表查询
由于二级索引的叶子节点不存储完整的表数据,索引当通过二级索引查询到聚簇索引列值后,还需要回到聚簇索引也就是表数据本身进一步获取数据。
回表查询需要额外的 B+Tree 搜索过程,必然增大查询耗时。
需要注意的是,通过二级索引查询时,回表不是必须的过程,当SELECT的所有字段在单个二级索引中都能够找到时,就不需要回表,MySQL称此时的二级索引为覆盖索引或触发了索引覆盖。
聚簇和非聚簇对比
3、按字段特性分类
MySQL索引按字段特性分类可分为:主键索引、普通索引、前缀索引。
- 主键索引
- 建立在主键上的索引被称为主键索引,一张数据表只能有一个主键索引,索引列值不允许有空值,通常在创建表时一起创建
- 唯一索引:建立在UNIQUE字段上的索引被称为唯一索引,一张表可以有多个唯一索引,索引列值允许为空,列值中出现多个空值不会发生重复冲突
- 普通索引
- 建立在普通字段上的索引被称为普通索引
- 前缀索引
- 前缀索引是指对字符类型字段的前几个字符或对二进制类型字段的前几个bytes建立的索引,而不是在整个字段上建索引
4、按索引字段个数分类
MySQL索引按字段个数分类可分为:单列索引、联合索引(复合索引、组合索引)。
- 单列索引
- 建立在单个列上的索引被称为单列索引
- 联合索引(复合索引、组合索引)
- 建立在多个列上的索引被称为联合索引,又叫复合索引、组合索引
使用索引的策略
什么时候索引会失效?
- 违反最左前缀原则
- 使用反向查询(!=, <>,NOT LIKE)
MySQL在使用反向查询(!=, <>, NOT LIKE)的时候无法使用索引,会导致全表扫描,覆盖索引除外。 - LIKE以通配符开头
但是使用通配符结尾就没有问题。 - 对索引列做任何操作
对索引列做了其他操作,例如数值计算、使用函数、(手动或自动)类型转换等操作,会导致索引失效。 - OR连接
使用OR连接的查询语句,如果OR之前的条件列是索引列,但是OR之后的条件列不是索引列,则不会使用索引。
索引创建(使用)原则
- 不为离散度高的列创建索引
- 只为用于搜索、排序或分组的列创建索引
- 用好联合索引
不要为联合索引的第一个索引列单独创建索引;
建立联合索引的时候,一定要把最常用的列放在最左边。 - 对过长的字段,建立前缀索引
- 频繁更新的值,不要作为主键或索引
6。 随机无序的值,不建议作为索引
例如身份证、UUID等。
什么情况下应不建或少建索引?
索引并不是万能的,在以下几个场景的时候,我们应该尽量不建或者说少建索引:
- 表记录太少
- 经常插入、删除、修改的表
- 数据重复且分布平均的表字段
- 假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率大约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度
- 经常和主字段一块查询但主字段索引值比较多的表字段