MySQL索引实现

一.索引的类型

1.1 B-Tree索引

 

       B-Tree实际上是一个术语,NDB使用了T-Tree存储索引,InnoDB使用B+Tree。B-Tree通常意味着所有的值是按顺序存储的,B-Tree索引能加快数据的访问速度,是因为存储引擎不再需要全表扫描来获取数据,而是从根节点向叶子结点搜索,通过匹配当前结点的值和要查找的值来确定是否继续向下查找。

MySQL索引实现_第1张图片

1.2 哈希索引

       基于哈希表实现,对于每一条数据,存储引擎都会对所有的索引列计算一个哈希码,不同的键值行计算的哈希码也不会一样。哈希索引将所有的哈希码都存到索引中,同时在哈希表中存储指向每一个数据行的指针。在MySQL中唯一支持哈希索引的是Memory引擎(InnoDB引擎有一个“自适应哈希索引,当InnoDB注意到某些索引值被使用频繁后,它会在内存中基于B-Tree索引之上再创建一个哈希索引,所以InnoDB的B-Tree索引也有哈希索引的一些优点”)。

1.3 空间数据索引

      MyISAM支持空间索引,空间索引无需前缀查询,查询时可以从任意维度来组合查询。

1.4 全文索引

     全文索引用来查找全文中的关键值。而不是直接来比较索引中的值,全文索引适用于match against操作而不是where的操作。

二.索引的优点

  • 索引减少了服务器需要扫描的数据量。
  • 索引帮助服务器避免排序和临时表(因为索引是有序的)。
  • 索引将随机IO变成顺序IO。

三.高性能索引策略

3.1 前缀索引和索引选择性

     如果索引很长的字符列,通常可以索引开始的部分字符,这样可以大大节约索引空间。当索引的选择性是1,性能是最好的。对于blob、text或者很长的varchar类型的列必须使用前缀索引,因为MySQL不允许索引这些列的完整长度。前缀索引的语法如下:

alter table stuGoldData add key (planId(3))

注意:前缀索引可以使查询更轻量,但MySQL无法使用前缀索引做order by 和 group by,也无法使用前缀索引做覆盖扫描。

3.2 多列索引

     在多个列建立独立的索引大部分情况下并不能提高MySQL的查询性能。MySQL5.0引入索引合并,可以定位多个索引列。注意:当服务器对多个索引做联合操作时通常会耗费大量CPU和内存。

3.3 确定索引列的顺序

select * from stuOnlineData where classId = 10086 and planId = 110112;

     这时候是创建一个(classId, planId)索引还是创建(planId, classId)可以跑一些查询来确定值在列中的分布情况来确定表中值的分布情况。

select sum(classId = 10086), sum(planId = 110112) from stuOnlineData;
sum(classId=10086):10000000000000000
sum(planId=110112):19

      所以我们需要把planId的值放在前面,因为planId的数量更小。

3.4 聚簇索引

  • 聚簇索引可以把线管数据保存在一起,例如实现电子邮箱时,可以根据用户ID聚集数据。
  • 聚簇索引将索引和数据保存在同一B-Tree中,因此从聚簇索引中获取数据通常比非聚簇索引中查询要快。
  • 使用覆盖索引扫描的查询可以直接使用叶子结点中的主键值。

3.5 覆盖索引

       如果索引的叶子结点已经包含要查询字段的值,即索引包含(覆盖)所有需要查询的字段的值就叫做副高索引。使用副高索引的好处:

  • 减少数据访问量,对于密集型IO很有帮助,索引相对于数据更小方便放入内存中。
  • 因为索引是按照列值顺序存储的,所以对于密集型IO查询会比随机查找快很多。
  • 一些存储引擎在内存中只缓存索引不缓存数据,使用覆盖索引访问数据会极大提升性能。
  • InnoDB使用聚簇索引,二级索引在叶子节点中保存了行的主键值,所以如果二级主键能覆盖查询就可以避免对主键索引的二次查询。

3.6 使用索引扫描来做排序

       MySQL有两种方式可以生成有序的结果:通过排序操作或者按索引扫描。按照索引操作是很快的只需从一个索引记录指向下一个紧接着的记录,但如果不能覆盖到查询所需的全部列,那就不得不每扫描一条索引就回表查询一次对应的行,杂合基本上是随机IO。如果explain出来的type的值为"index",则说明MySQL使用了索引扫描排序。

3.7 压缩前缀索引

       MyISAM使用前缀压缩来减少索引大小,从而让更多的索引放入内存。默认情况下只压缩字符串,也可以通过参数设置int等整数类型被压缩。MyISAM压缩的方式是存储前缀字节数和剩余的不同后缀部分。例如第一个索引值是“perform”,第二个索引值是“performance”,那么第二个索引值压缩格式就是“7,ance”这种形式。

3.8 避免创建重复索引

       在相同的列上按照相同的顺序创建相同的索引会增加额外维护的成本,优化器工作的时候会增加额外的判断和执行过程。要避免这种创建。

3.9 删除从未使用的索引

       从未使用的索引会增加查询的成本,我们应该查询一段时间内每个索引的使用频率,删除这些从未使用的索引。

4.0 使用索引尽量避免锁

      尽管InnoDB的行锁效率很高,再使用索引时也要避免不必要的行锁,这回带来额外开销。例如:

select * from stuData where stuId < 5 and stuId >1 for update;

       这条查询仅仅返回3条数据,但获取了4行的排他锁。这是因为在执行 where stuId < 5的时候存储引擎就已经返回1-4行的数据了,所以获取了1-4的行锁。 

四.索引的实现

4.1 为什么选用B+树

4.1.1 二叉查找树

MySQL索引实现_第2张图片

       如上图,我们创立一个“二叉索引树”。二叉索引树特点就是任何结点的左孩子结点都小于当前结点,右叶孩子结点点都大于当前结点。 假如我们需要查找结点值为12的值,需要3次比较,查找流程如下:

  • 从根节点遍历,12大于10在右子树向下遍历。
  • 12小于13,继续向左子树遍历。
  • 12等于12,取出data,即 string = xm。

4.1.2 平衡二叉树

      如果上述的二叉查找树的插入序列是有序的(升序或者降序),二叉查找树会退化成链表,复杂度从O(logn)降为O(n)插入数据是(5,7,8,10,12,13,17)如下图所示:

MySQL索引实现_第3张图片

       为了避免这种情况就出现了具有平衡能力的二叉搜索树,其中AVL树是最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是O(logN)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。上图就是一个平衡二叉树。

4.1.3 B树

       如果我们采取上面的两种树的结构存储数据,那我们查找一个结点就需要从磁盘中读取一个结点(也就是一个磁盘块,从磁盘中读取数据都是按照磁盘块来读)。如果数据量非常大,二叉树的结点会非常多,树的高度将会变得非常大,查找数据将会进行多次磁盘IO效率很低。为了解决这个问题,我们找寻一种单个结点存储多个键值和数据的树状结构,B树可以满足所有要求。

MySQL索引实现_第4张图片

       从上图可以看出,B树相对于平衡二叉树,每个结点存储了更多的键值(key)和数据(data),并且每个结点拥有更多的子节点,子节点的个数称为阶,上述 图中的B树为3阶B树,高度也会很低。查找id=28的用户信息,所需要的流程如下:

  • 先找到根节点也就是页1,判断28在键值17和35之间,我们根据页一中的指针p2找到页3。
  • 将28与页3中的键值比较,28在26与33之间,根据页3中的指针p2找到页8。
  • 将28和页8中的键值比较,发现匹配值28,将28对应的用户信息取出。

4.1.4 B+树

MySQL索引实现_第5张图片

 

       相对于B树,B+树只有叶子结点存储数据,非叶子结点仅存储键值,增加了数据顺序访问指针,每个结点都指向相同结点的地址(B树的结点不仅存键值也会存储数据)。选用B树的原因在于数据库中页的大小是固定的,如果将键值和数据分开存储将会存储更多的值,这样查找数据时IO次数将再次减少,查询效率变快。数据页之间通过双向链表连接以及叶子结点中数据之间通过单向链表的连接方式可以快速找到表中所有的数据。

4.1.5 总结

       任何不考虑应用场景的设计都不是最好的设计,当我们明确的定义了使用 MySQL 时的常见查询需求并理解场景之后,再对不同的数据结构进行选择就成了理所当然的事情,当然 B+ 树可能无法对所有 OLTP 场景下的查询都有着较好的性能,但是它能够解决大多数的问题。

    我们在这里重新回顾一下 MySQL 默认的存储引擎选择 B+ 树而不是哈希或者 B 树的原因:

  • 哈希虽然能够提供 O(1) 的单数据行操作性能,但是对于范围查询和排序却无法很好地支持,最终导致全表扫描;
  • B 树能够在非叶节点中存储数据,但是这也导致在查询连续数据时可能会带来更多的随机 I/O,而 B+ 树的所有叶节点可以通过指针相互连接,能够减少顺序遍历时产生的额外随机 I/O;

       如果想要追求各方面的极致性能也不是没有可能,只是会带来更高的复杂度,我们可以为一张表同时建 B+ 树和哈希构成的存储结构,这样不同类型的查询就可以选择相对更快的数据结构,但是会导致更新和删除时需要操作多份数据。

4.2 聚簇索引和非聚簇索引

  • 聚簇索引:以InnoDB作为存储数据引擎的表,表中数据都会有一个主键,即使你不创建主键,系统也会帮你创建一个隐形主键。这是因为InnoDB是把数据存放在B+树中的,而B+树的键值就是主键,在B+树的叶子结点中,存储了表中所有的数据。这种以主键作为B+树索引的键值而构建的B+树索引,我们称之为聚簇索引。
  • 非聚簇索引:以主键以外的列值作为键值构建B+树的索引,我们称之为非聚簇索引。非聚簇索引与聚簇索引的区别在于非聚簇索引的叶子结点不存储表中数据,而是存储该列对应的主键,想要查找数据需要根据主键再去聚集索引中查找,这个根据聚簇索引查找数据的过程叫做回表。

4.2.1 利用聚簇索引查找数据

MySQL索引实现_第6张图片

       上图就是聚簇索引,我们想要查询id>=18并且id<40的用户数据,SQL语句如下(id为主键):

select *from user where id >= 18 and id < 40;

       具体查找过程如下:

  • 根节点常驻内存,我们先要查找键值18,根据页1中的p2指针定位到页3.
  • 要从页3查收数据,我们需要拿着P2指针去磁盘中读取页3.从磁盘中去读页3后将页3放入内存中,然后查找。拿到键值18,再用页3中的指针p1定位到页8。
  • 页8不在内存中,将页8读到内存中。因为页中的数据是链表进行连接的,而且主键是按照顺序存放的,此时可以根据二分法定位到键值18。
  • 定位到是范围查找,所有的庶几乎又都存储在叶子结点并且是有序排列的,对页8中的键值一次遍历并匹配满足条件的数据,一直找到键值为22的数据,然后8中没有数据了,紧接着用页8中的指针p去读页9中的数据。
  • 页9不在内存中,加载页9到内存中,和页8一样的方式查找直到页12加载到内存中,发现41 > 40,此时不满足条件查找终止。

4.2.2 利用非聚簇索引查找数据

MySQL索引实现_第7张图片

       键值匹配跟聚簇索引的流程是一样的,-左面的值是键值,右面的值是主键值,如果我们要查找幸运数字是33的用户信息,对应的sql语句如下:

select * from user where luckNum = 33;

      最终我们查到luckNum值为33的主键值是47,然后我们再回到聚簇索引中查找数值。

你可能感兴趣的:(MySQL)