mysql原理2-索引

2021/01/06 Mysql 共 6434 字,约 19 分钟

B+ Tree原理

1. 数据结构

B Tree 指的是 Balance Tree,也就是平衡树。平衡树是一颗查找树,并且所有叶子节点位于同一层。

B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。

在 B+ Tree 中,一个节点中的 key 从左到右非递减排列,如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,且不为 null,则该指针指向节点的所有 key 大于等于 keyi 且小于等于 keyi+1。

image-20210113205819442

2. 操作

进行查找操作时,首先在根节点进行二分查找,找到一个 key 所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的 data。

插入删除操作会破坏平衡树的平衡性,因此在进行插入删除操作之后,需要对树进行分裂、合并、旋转等操作来维护平衡性。

3. 与红黑树的比较

红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree 作为索引结构,这是因为使用 B+ 树访问磁盘数据有更高的性能。

(一)B+ 树有更低的树高

平衡树的树高 O(h)=O(logdN),其中 d 为每个节点的出度。红黑树的出度为 2,而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多。

(二)磁盘访问原理

操作系统一般将内存和磁盘分割成固定大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点。

如果数据不在同一个磁盘块上,那么通常需要移动制动手臂进行寻道,而制动手臂因为其物理结构导致了移动效率低下,从而增加磁盘数据读取时间。B+ 树相对于红黑树有更低的树高,进行寻道的次数与树高成正比,在同一个磁盘块上进行访问只需要很短的磁盘旋转时间,所以 B+ 树更适合磁盘数据的读取。

(三)磁盘预读特性

为了减少磁盘 I/O 操作,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的磁盘旋转时间,速度会非常快。并且可以利用预读特性,相邻的节点也能够被预先载入。

4. B+tree性质:

1.)n棵子tree的节点包含n个关键字,不用来保存数据而是保存数据的索引。

2.)所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。

3.)所有的非终端结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字。

4.)B+ 树中,数据对象的插入和删除仅在叶节点上进行。

5.)B+树有2个头指针,一个是树的根节点,一个是最小关键码的叶节点。

5. B树和B+树的区别

  • 在B树中,你可以将键和值存放在内部节点和叶子节点;但在B+树中,内部节点都是键,没有值,叶子节点同时存放键和值。
  • B+树的叶子节点有一条链相连,而B树的叶子节点各自独立。

image-20210106224625313

6. 数据库为什么使用B+树而不是B树

  • B树只适合随机检索,而B+树同时支持随机检索和顺序检索;

  • B+树空间利用率更高,非叶子节点占用空间小,可减少I/O次数,磁盘读写代价更低。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素;

  • B+树的查询效率更加稳定。B树搜索有可能会在非叶子结点结束,越靠近根节点的记录查找时间越短,只要找到关键字即可确定记录的存在,其性能等价于在关键字全集内做一次二分查找。而在B+树中,顺序检索比较明显,随机检索时,任何关键字的查找都必须走一条从根节点到叶节点的路,所有关键字的查找路径长度相同,导致每一个关键字的查询效率相当。

  • B-树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作。

  • 增删文件(节点)时,效率更高。因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。

MySQL 索引

索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。

1. B+Tree 索引

是大多数 MySQL 存储引擎的默认索引类型。

因为不再需要进行全表扫描,只需要对树进行搜索即可,所以查找速度快很多。

因为 B+ Tree 的有序性,所以除了用于查找,还可以用于排序和分组。

可以指定多个列作为索引列,多个索引列共同组成键。

适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。

B+ 树的非叶子节点存 m-1 个 key,叶子节点存 m-1 个 key-value。

B+ 树的 m 取决于磁盘页大小、key 大小、指针大小,m = (磁盘页大小) / (指针大小 + key 大小)。 数据库常见 m ≈ 100~200。

InnoDB 的 B+Tree 索引分为主索引和辅助索引。主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。

image-20210113205854610

辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到主索引中进行查找。

image-20210113205903435

2. 哈希索引

哈希索引能以 O(1) 时间进行查找,但是失去了有序性:

  • 无法用于排序与分组;
  • 只支持精确查找,无法用于部分查找和范围查找。

InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。

3. 全文索引

MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。

查找条件使用 MATCH AGAINST,而不是普通的 WHERE。

全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射。

InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。

4. 空间数据索引

MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。

必须使用 GIS 相关的函数来维护数据。

联合索引原理

比如联合索引(a, b)

  • 叶子节点拥有联合索引中的所有字段以及主键字段,且叶子节点数据局部有序

  • 索引类型非叶子节点存储内容叶子节点存储内容
    单列索引 (a)仅存 aa + 主键(如果是二级索引)
    联合索引 (a, b)存储 (a, b)(a, b) + 主键(如果是二级索引)

    📌 重点区别

    • 联合索引 (a, b) 的非叶子节点 **可能不完整存储 b
    • 在大多数情况下,非叶子节点只存 a,但在 a 不是唯一键时,可能存储部分 b 以优化查找效率。MySQL 不会让非叶子节点存储过多额外数据,否则会影响查询性能和索引大小。
    • 单列索引 (a) 的非叶子节点 只存 a,结构更简单。
    • 在 B+ 树的非叶子节点中,存储的键值用于索引查找,它不需要存储所有的列值,只需要保证可以正确分区查找。

什么时候不会新增非叶子节点?

  • 叶子节点未满时,直接插入。假设 每个叶子节点最多能存储 4 条数据,4以内都可以不新增

  • 仅在叶子节点分裂时,才会更新非叶子节点!类似红黑树,向上传递分裂。只有当非叶子节点也满时,才会新增非叶子层级!
  • 删除时,数据量低于一半,会和兄弟借一个kv或节点合并

索引设计及优化

索引设计的原则

索引虽好,但也不是无限制的使用,最好符合一下几个原则

1) 最左前缀匹配原则,组合索引非常重要的原则,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的顺序可以任意调整。

2)较频繁作为查询条件的字段才去创建索引

3)更新频繁字段不适合创建索引

4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)

5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。

8)对于定义为text、image和bit的数据类型的列不要建立索引。对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。

9) 基数大的索引在前。

10) 如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间

索引失效情况

参考,重要的已摘要

image-20220107220243341

和索引顺序无关,MySQL底层的优化器会进行优化,调整索引的顺序。

索引失效情况:

  • 违反最左前缀法则会索引失效: 如果索引有多列,要遵守最左前缀法则, 即查询从索引的最左前列开始并且不跳过索引中的列

  • 在索引列上做任何操作会索引失效: 如计算、函数、(自动or手动)类型转换等操作,会导致索引失效从而全表扫描

  • 索引范围条件右边的索引列会失效

    • select * from myTest  where a=3 and b>7 and c=3;     ---- b范围值,断点,阻塞了c的索引
      a用到了,b也用到了,c没有用到,这个地方b是范围值,也算断点,只不过自身用到了索引
          
          
      salary > 5000 AND salary < 50000 或者between算一个范围,有效的。 只有右边的索引会失效。
          
          
      SELECT * FROM users WHERE age = 30 AND salary LIKE '%5000%' AND city = 'Beijing';
      salary是范围查询,city索引会失效。
          
      也就是说一个sql里最多只能有一个范围查询,还需要这个范围查询在最后才行。
      
  • 在使用不等于(!=)的时候无法使用索引会导致全表扫描(除覆盖索引外)

  • like以通配符开头(’%abc’)会索引失效。因为是字典序排序的。 但前缀索引是有效的,可以当范围索引使用

  • 字符串不加单引号索引失效 会索引失效。类型转换导致索引失效

  • or连接 会索引失效。如果 OR 两侧的列分别有索引,可以使用 UNION ALL 分开查询。MySQL 5.6+ 支持 INDEX MERGE 优化,允许 OR 语句分别使用索引,然后union合并结果

  • order by会索引失效,正常(索引参与了排序)备注:索引有两个作用:排序和查找。实际上是先order by然后order by用索引进行了排序。

  • group by会索引失效, 正常(索引参与了排序)备注:分组之前必排序(排序同order by)

  • null不在索引树上,但在叶子节点上。NULL 值通常排在最前面,但它不会影响索引的查询。唯一索引允许多个 NULL。NULL 不是一个具体值,不能进行比较。where xx is null 可能利用索引,但性能差,仅扫描叶子结点。 所以基本可以认为null是全表扫描,索引失效。具体看mysql优化情况。

覆盖索引

索引包含所有需要查询的字段的值。对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。

如果执行的语句是 select ID from T where k between 3 and 5,这时只需要查 ID 的值,而 ID 的值已经在 k 索引树上了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引 k 已经“覆盖了”我们的查询需求,我们称为覆盖索引。

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

索引下推

索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。

查询性能优化

使用 Explain 进行分析

Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。

比较重要的字段有:

  • select_type : 查询类型,有简单查询、联合查询、子查询等
  • key : 使用的索引
  • rows : 扫描的行数

优化数据访问

1. 减少请求的数据量
  • 只返回必要的列:最好不要使用 SELECT * 语句。
  • 只返回必要的行:使用 LIMIT 语句来限制返回的数据。
  • 缓存重复查询的数据:使用缓存可以避免在数据库中进行查询,特别在要查询的数据经常被重复查询时,缓存带来的查询性能提升将会是非常明显的。
2. 减少服务器端扫描的行数

最有效的方式是使用索引来覆盖查询。

索引实现

Innodb 引擎和 Myisam 引擎的实现

两者都是B+树索引

  • MyISAM:索引存储的是数据的物理地址,查询快,但不支持事务。

  • InnoDB:主键索引存储整行数据,辅助索引存储主键值,可能需要二次回表,但支持事务,适合高并发场景。

对比项MyISAMInnoDB
存储文件frm(表结构)
MYD(数据)
MYI(索引)
frm(表结构)
idb(数据 + 索引)
索引存储非聚簇索引(数据和索引分离)聚簇索引(数据和索引一起存储)
主键索引主索引 B+ 树,叶子节点存的是数据物理地址主键索引 B+ 树,叶子节点存的是整行数据
辅助索引存储的是数据物理地址存储的是主键值,需要回表查询
查询效率索引叶子节点直接存数据地址主键查询快,但辅助索引查询需要二次回表(若无索引覆盖)
适用场景读多写少,对 SELECT 查询友好事务处理、并发、更新操作多的场景
  • 聚簇索引的叶子节点存储的就是完整的行数据,而非仅仅是指向数据的指针。

    这样,索引本身就是数据,数据就是索引,自然存储在同一个文件中。

  • 多个索引都会存储在同一个 .ibd 文件中,但它们的 B+ 树结构是分开的。

  • Innodb 关键特性

    • 主键索引(聚簇索引)
      • 叶子节点存储整行数据。
      • 一个表只能有一个聚簇索引(通常是 PRIMARY KEY)。
      • 如果没有主键,InnoDB 会自动创建隐藏主键。
    • 辅助索引(二级索引)
    • 叶子节点存储的是主键值,查询时可能需要回表查询数据。
  • MyISAM 关键特性

    • 非聚簇索引
      • 索引叶子节点存的是数据的物理地址,索引和数据文件是分开的。
      • 查询效率高(索引直接定位物理位置)。
    • 索引存储
      • 主索引和辅助索引存储结构相同,都存储数据的物理地址。需要再寻址一次才能得到数据。

为什么使用辅助索引

因为 InnoDB 需要节省存储空间。一个表里可能有很多个索引,InnoDB 都会给每个加了索引的字段生成索引树,如果每个字段的索引树都存储了具体数据,那么这个表的索引数据文件就变得非常巨大(数据极度冗余了)

查询性能比较,myisam查询更快

MyISAM 直接找到物理地址后就可以直接定位到数据记录,但是 InnoDB 查询到叶子节点后,还需要再查询一次主键索引树,才可以定位到具体数据。等于 MyISAM 一步就查到了数据,但是 InnoDB 要两步,那当然 MyISAM 查询性能更高。

Innodb聚簇索引实现

参考

https://smartkeyerror.com/MySQL-physical-structure

索引里的指针都是文件偏移量。实际上就是个链表。

文件通过页面缓存,都是按页读的,比如一页4KB.

因此聚簇索引是按主键id有序存的,但不会改动其他数据偏移位置。本质上是个链表,每条数据指向了下条数据以及上条数据偏移量也就是指针。

聚簇索引具有唯一性,由于聚簇索引是将数据跟索引结构放到一块,因此一个表仅有一个聚簇索引。

表中行的物理顺序和索引中行的物理顺序是相同的,在创建任何非聚簇索引之前创建聚簇索引,这是因为聚簇索引改变了表中行的物理顺序,数据行 按照一定的顺序排列,并且自动维护这个顺序;

聚簇索引默认是主键

参考

参考

Search

    Table of Contents