MySQL详解之深入浅出索引

目录

MySQL详解之深入浅出索引

MySQL有两种存储引擎:MyISAM和InnoDB。下面我们主要介绍InnoDB类型的存储引擎。

索引的作用:提高数据查询效率。

为什么要使用索引

因为索引可以使得我们避免全表扫描去查找数据,提高查找效率。

索引的常见模型

常见的索引模型:

  • 哈希表
  • 有序数组
  • 搜索树

哈希表

哈希表是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的值即 key,就可以找到其对应的值即 Value。哈希的思路很简单,把值放在数组里,用一个哈希函数把 key 换算成一个确定的位置,然后把 value 放在数组的这个位置。

如图:
11c4db2bc6f2cd8f6c4d72b5870e3c09.png

缺点:

  • 仅仅能满足“=”,“IN”,不能使用范围查询
  • 无法被用来避免数据的排序操作
  • 不能利用部分索引建查询
  • 不能避免表扫描
  • 遇到大量Hash值相等的情况后性能并不一定就会比B+ Tree索引高

哈希表这种结构在新增时会很快,但是因为不是有序的,所以哈希索引做区间查询的速度是很慢的。所以适用于只有等值查询的场景,比如Redis以及其他一些NoSQL引擎。

有序数组

这种索引结构支持范围查询,因为它是根据索引递增顺序保存的,如果我们要查找其中的某个值,根据二分查找可以很快的得到,时间复杂度是O(log(N))。同时在做区间查询的时候,查询效率也是很优秀的。如果仅仅看查询效率,有序数据就是最好的数据结构了。但是,在需要更新数据的时候就麻烦了,因为在向中间插入一条记录时,需要挪动后面的所有记录,成本太高了。

所以有序数组在等值查询和范围查询场景中的性能就都非常优秀。

搜索树

这里先简单介绍一下B Tree和B+ Tree。

B Tree

如图所示:
3bbf4b3e3ffe6c614fa0352ef06ca54e.png

定义:

  • 根节点至少包括两个孩子
  • 树中每个节点最多含有m个孩子(m>=2)
  • 除根节点和叶节点外,其他每个节点至少有ceil(m/2)个孩子(ceil表示向上取整)
  • 所有叶子节点都位于同一层

PS:图中第二行第二个节点的叶子节点下面只有一个子节点,错误!

B+ Tree

B+树是B树的辩题,其定义基本与B树相同,除了:

  • 非叶子节点的子树指针与关键字个数相同
  • 非叶子节点的子树指针P[i],指向关键字值[K[i],K[i+1])的子树
  • 非叶子节点仅用来索引,数据都保存在叶子节点中
  • 所有叶子节点均有一个链指针指向下一个叶子节点

经过比较,可以得知:B+ Tree更适合用来存储索引

  • B+树的磁盘读写代码更低
  • B+树的查询效率更加稳定(O(log(n)))
  • B+树更有利于对数据库的扫描

这也是为什么现在数据库主要选择B+树做索引的原因。

二叉树的搜索效率是最高的,但是实际上大多数的数据库存储并不使用二叉树,其原因是,索引不止存在内存中,还要写到磁盘上。

BitMap

位图索引,这种索引很少数据库支持,oracle是支持这种索引的,它一般适用于该字段的值就是几个值的情况,比如性别,颜色等等。只做了解。

InnoDB的索引模型

InnoDB中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表称为索引组织表。又因为前面我们提到的,InnoDB使用了B+树索引模型,所以数据都是存储在B+树中的。每一个索引在InnoDB里面对应一棵B+树。

根据叶子节点的内容,索引类型分为:

  • 主键索引
  • 非主键索引

主键索引的叶子节点存的是整行数据。在InnoDB里,主键索引也被称为聚簇索引(clustered index)。
非主键索引的叶子节点内容是主键的值。在InnoDB里,非主键索引也被称为二级索引(secondary index)。

基于主键索引和普通索引的查询有什么区别?
基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。

我们讨论下面一个案例:

分析一下哪些场景下建表时需要使用自增主键,哪些场景下不应该?

  • 使用自增主键:

    自增主键的插入数据模式,正符合了我们前面提到的递增插入的场景。每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。

  • 使用业务逻辑字段做主键:

    往往不容易保证有序插入,这样写数据成本相对较高。

  • 另外我们从存储空间的角度来看

    假设你的表中确实有一个唯一字段,比如字符串类型的身份证号,那应该用身份证号做主键,还是用自增字段做主键呢?
    由于每个非主键索引的叶子节点上都是主键的值。如果用身份证号做主键,那么每个二级索引的叶子节点占用约 20 个字节,而如果用整型做主键,则只要 4 个字节,如果是长整型(bigint)则是 8 个字节。

显然,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间就越小。
所以,从性能和存储空间方面考量,自增主键往往是更合理的选择。

B+ 树能够很好地配合磁盘的读写特性,减少单次查询的磁盘访问次数。

重建索引

重建普通索引可以达到省空间的目的;但是如果重建主键索引,则相当于整个表重建,因为InnoDB是一种索引组织表,也就是说表是根据主键顺序以索引形式存放的。
重建表可以使用以下语句:alter table T engine=InnoDB,其中T为表名。

覆盖索引

我们先看下面这个例子:
有一个用户表User,初始化语句如下:

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `age_inx` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

INSERT INTO `user` VALUES (1, '张三', 10);
INSERT INTO `user` VALUES (2, '李四', 30);
INSERT INTO `user` VALUES (3, '王五', 35);
INSERT INTO `user` VALUES (4, '赵六', 40);
INSERT INTO `user` VALUES (5, '小张', 45);

主键为:id,在字段age上新建索引为age_inx

那么如果执行select * from user where age between 30 and 40会执行几次树的搜索,会扫描多少行?

现在我们一起来看看这条SQL查询语句的执行流程:

1.在age索引树上找到age=30的记录,取得id=2;
2.再到id索引树查到id=2对应的记录;
3.在age索引树上找到age=35的记录,取得id=3;
4.再到id索引树查到id=3对应的记录;
5.在age索引树上找到age=40的记录,取得id=4;
6.再到id索引树查到id=4对应的记录;
7.在age索引树取下一个值age=45,不满足条件,循环结束。

在这个过程中,回到主键索引树搜索的过程,我们成为回表。在这个例子中,由于查询结果所需要的数据只在主键索引上有,所以不得不回表。这个时候我们可以对语句做如下优化:select id from user,这时只需要查id的值,而id的值已经在age索引树上了。因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引age已经覆盖了我们的查询需求,我们成为覆盖索引

由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

最左前缀原则

B+ 树这种索引结构,可以利用索引的“最左前缀”,来定位记录。

基于上面对最左前缀索引的说明,我们来讨论一个问题:在建立联合索引的时候,如何安排索引内的字段顺序。

索引的复用能力,因为可以支持最左前缀,所以当已经有了 (a,b) 这个联合索引后,一般就不需要单独在 a 上建立索引了。因此,第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。

最左前缀可以用于在索引中定位记录。

索引下推

在MySQL5.6之前,只能一个个回表到主键索引上找出数据行,再对比字段值
在MySQL5.6引入的索引下推优化,可以在索引遍历过程中,对索引中包含的字段优先做判断,直接过滤掉不满足条件的记录,减少回表次数。

索引是建立得越多越好吗?

  • 数据量小的表不需要建立索引,建立会增加额外的索引开销
  • 数据变更需要维护索引,因为更多的索引意味着更多的维护成本
  • 更多的索引意味着也需要更多的空间

总结

在满足语句需求的情况下, 尽量少地访问资源是数据库设计的重要原则之一。我们在使用数据库的时候,尤其是在设计表结构时,也要以减少资源消耗作为目标。

猜你喜欢

转载自www.cnblogs.com/xiaotutu365/p/10339204.html