【MySQL】-【为什么使用索引】-【InnoDB中索引的推演】-【MyISAM中的索引方案】-【索引的代价】-【MySQL数据结构选择的合理性】

为什么使用索引

一、hashmap底层使用红黑树
二、索引时在存储引擎中实现的,因此不同存储引擎的索引可能不同
索引的优点:

  1. 类似大学图书馆建书目索引,可以减少磁盘I/O的次数,加快查询速率
  2. 通过创建唯一索引,可以保证数据库表中每一行数据的唯一性,如果这个数据建了索引,那么会自动给他加上唯一约束 。
  3. 在实现数据的参考完整性方面,可以加速表和表之间的连接 。换句话说,对于有依赖关系的子表和父表联合查询时,可以提高查询速度
  4. 在使用分组和排序子句进行数据查询时,可以显著减少查询中分组和排序的时间 ,降低CPU的消耗。因为索引是排好序的能快速查找的数据结构,既然排好序,那进行分组和排序时会更高效

缺点:

  1. 创建索引和维护索引要耗费时间,随着数据量的增加,所耗费的时间也会增加。
  2. 索引存储在磁盘上,需要占磁盘空间
  3. 虽然索引大大提高了查询速度,但是当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。因为索引是排好序的能快速查找的数据结构,假如索引现在是1,1,2,2,3,4,4,假如要增加一个索引3,那么4,4,需要移动,删除和修改同理。如何解决这个问题:在维护表时,先删除表中的索引,然后插入数据,插入完成后再建索引

InnoDB中索引的推演

假如要查找一些数据:

索引前的查找

一、在一个页中查找
在这里插入图片描述
数据通过单链表的形式存储,不然你每次删除条数据,后面的数据还要往前移
二、在很多页中查找:确定数据在哪一页,从所在页中查找相应的数据(此时的查找套路同【在一个页中查找】),但是在没有索引的情况下,不论是根据主键列或者其他列的值进行查找,由于我们并不能快速的定位到记录所在的页,所以只能从第一个页沿着双向链表一直往下找,在每一个页中根据我们上面的查找方式去查找指定的记录。因为要遍历所有的数据页,所以这种方式显然是超级耗时的。如果一个表有一亿条记录呢?此时索引应运而生。

设计索引

简单的索引设计方案

创建一个表,表中的字段如下,
在这里插入图片描述

record_type:表示记录的类型,0表示普通记录、2表示最小记录(即这条数据是本页的最小数据)、3表示最大记录(即这条数据是本页的最大数据)、1暂时还没用过
next_record:表示下一条数据所在地址相对于本条记录的地址偏移量,本文用箭头来表明下一条记录是谁。
c1、c2、c3列:记录字段值

那么一页中的数据就是这样:
在这里插入图片描述
假如数据有很多,一页放不下,并且要求按照c1列大小递增排列:
在这里插入图片描述
假如我们现在想查找某条数据,我们只能从头开始遍历,效率太慢。如果想快速定位到需要查找的数据在哪页中该咋办?我们可以为快速定位记录所在的数据页而建立一个目录 ,建这个目录必须完成下边这些事:

  1. 下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。
  2. 给所有的页建立一个目录项。

所以我们为上边几个页做好的目录就像这样子:
在这里插入图片描述
以页28为例,它对应目录项2,这个目录项中包含着该页的页号28以及该页中用户记录的最小主键值(c1)5 (注意:要求按照c1列大小递增排列)。我们只需要把目录项在物理存储器上连续存储(比如:数组),就可以实现根据主键值快速查找某条记录的功能了。比如:查找主键值为20的记录:

  1. 先从目录项中根据二分法快速确定出主键值为20的记录在目录项3中(因为12<20<209 ),它对应的页是页9 。
  2. 再根据前边说的在页中查找记录的方式去页9中定位具体的记录。

这个目录有一个别名,称为索引 。

InnoDB中的索引方案

如前所述,如果目录项在物理存储器上连续存储(比如:数组),假如我删了一页数据,那么该页在目录项的数据也要删除,那么后面的数据得向前移,这是个弊端,那我们可以让目录项也像数据那样使用单向链表进行连接
在这里插入图片描述
如果目录项记录太多,一页放不下,也可以增加一些记录目录项的页
在这里插入图片描述
如果记录目录项的页太多了,当我们查找某条数据在哪页时,又需要从第一条目录项开始查找,效率太低,那我们可以给目录项记录页创建目录页
在这里插入图片描述
这个数据结构,它的名称是 B+树 。
假设所有存放用户记录的叶子节点代表的数据页可以存放100条用户记录,所有存放目录项记录的内节点代表的数据页可以存放1000条目录项记录,那么:
如果B+树只有1层,也就是只有1个用于存放用户记录的节点,最多能存放100条记录。
如果B+树有2层,最多能存放 1000×100=10,0000 条记录。
如果B+树有3层,最多能存放 1000×1000×100=1,0000,0000 条记录。
如果B+树有4层,最多能存放 1000×1000×1000×100=1000,0000,0000 条记录。相当多的记录!!!你的表里能存放 100000000000 条记录吗?所以一般情况下,我们用到的B+树都不会超过4层,这是因为树的层次越低,IO次数越少

只要目录页多余一个,就为目录页再构建个目录页

常见的索引

一、索引可以分为2种:聚簇(聚集)和非聚簇(非聚集)索引,非聚集索引也称为二级索引或者辅助索引。
二、一张表只能有一个聚集索引,可以有多个二级索引
三、如果我们依赖于主键进行查找,那就用聚簇索引,如果想用别的非主键字段查找,那就用二级索引

聚簇索引

一、聚簇索引是一种数据存储方式,所有的用户记录都存储在了叶子结点,也就是所谓的索引即数据,数据即索引。我们前面讲的那个b+树就是聚簇索引

聚簇表示数据行和相邻的键值聚簇的存储在一起
聚集索引:主键构建的索引

二、特点

  1. 使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义:
    (1)页内的记录是按照主键的大小顺序排成一个 单向链表 。
    (2)各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个 双向链表 。
    (3)存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表
  2. B+树的叶子节点存储的是完整的用户记录。
    完整的用户记录:所有列的值(包括隐藏列)。

三、优点

  1. 数据访问更快 ,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快
  2. 聚簇索引对于主键的排序查找范围查找速度非常快。按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以节省了大量的io操作

四、缺点:

  1. 插入速度严重依赖于插入顺序 ,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键
  2. 更新主键的代价很高 ,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新
  3. 二级索引访问需要两次索引查找 ,第一次找到主键值,第二次根据主键值找到行数据

二级索引(辅助索引、非聚簇索引)

一、聚簇索引只有在搜索条件是主键,因为b+树中的数据都是按照主键进行排序的。如果现在我们想通过c2列进行数据查找,我们就可以构建一个以c2排序的b+树,这就是二级索引。
非聚簇索引
二、特点:

  1. 使用记录c2列的大小进行记录和页的排序,这包括三个方面的含义:(1)页内的记录是按照c2列的大小顺序排成一个 单向链表。
    (2)各个存放 用户记录的页 也是根据页中记录的c2列大小顺序排成一个双向链表。
    (3)存放 日录项记录的页 分为不同的层次,在同一层次中的页也是根据页中目录项记录的c2列大小顺序排成一个双向链表。
  2. B+树的叶子节点存储的并不是完整的用户记录,而只是C2列+主键这两个列的值。
  3. 目录项记录中不再是 主键+页号 的搭配,而变成了c2列+页号的搭配。

三、如果查询条件是select * from 表名 where c2 = 值,那么底层查找原理:先根据二级索引(上面这棵b+树)找到主键,然后再拿着主键去聚簇索引(上上面那棵b+树)上查其余内容,此时需要使用两棵b+树,这个过程也称为回表,就是要回到聚簇索引中查找。

为什么我们还需要一次回表操作呢?直接把完整的用户记录放到叶子节点不OK吗:如果把完整的用户记录放到叶子节点是可以不用回表。但是太占地方了,相当于每建立一棵B+树都需要把所有的用户记录拷贝一遍,这就有点太浪费存储空间了。

四、非聚簇索引的存在不影响数据在聚簇索引中的组织,即不会影响数据表的物理存储顺序,所以一张表可以有多个非聚簇索引。
五、聚簇索引与非聚簇索引l的原理不同,在使用上也有一些区别:

  1. 聚族索引的叶子节点存储的就是数据记录,非聚族索引的叶子节点存储的是数据位置。非聚族索引不会影响数据表的物理存储顺序。
  2. 一个表只能有一个聚筷索引,因为只能有一种排序存储的方式,但可以有 多个非聚筷索引,也就是多个索引目录提供数据检索。
  3. 使用聚族索引的时候,数据的查询效率高,因为不用回表。但如果对数据进行插入,删除,更新等操作,效率会比非聚簇索引低。他这里的意思是说,假如我要更新c3的值,那么聚簇索引会涉及到更改c3的值,但是二级索引不会,因为二级索引中压根就没有c3

联合索引

一、我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按照c2和c3列的大小进行排序:

  1. 先把各个记录和页按照c2列进行排序。
  2. 在记录的c2列相同的情况下,采用c3列进行排序

请添加图片描述
二、如图所示,我们需要注意以下几点:

  1. 每条目录项记录都由c2、c3、页号这三个部分组成,各条记录先按照c2列的值进行排序,如果记录的c2列相同,则按照c3列的值进行排序。
  2. B+树叶子节点处的用户记录由c2、c3和主键C1列组成。

三、这种以c2和c3列的大小为排序规则建立的B+树称为联合索引,本质上也是一个二级索引。它的意思与【分别为c2和c3列分别建立索引】的表述是不同的,不同点如下:

  1. 建立联合索引只会建立如上图一样的1棵B+树。
  2. 为c2和c3列分别建立索引会分别以c2和c3列的大小为排序规则建立2棵B+树。

InnoDB的B+树索引的注意事项

根页面位置万年不动

我们前边介绍B+村索引的时候,为了大家理解上的方便,先把存储用户记录的叶子节点都画出来,然后接着画存储目录项记录的内节点,实际上B+树的形成过程是这样的:

  1. 每当为某个表创建一个B+树索引(聚族索引1不是人为创建的,默认就有)的时候,都会为这个索引创建个根节点页面。最开始表中没有数据的时候,每个B+树素引1对应的根节点中既没有用户记录,也没有目录项记录。
  2. 随后向表中插入用户记录时,先把用户记录存储到这个根节点中。当根节点中的可用空间用完时继续插入记录,此时会将根节点中的所有记录复制到一个新分配的页,比如页a中,然后对这个新页进行页分裂的操作,得到另一个新页,比如页b。这时新插入的记录根据键值(也就是
    聚筷索引中的主键值,二级索引中对应的索引列的值)的大小就会被分配到页a或者页b中,而根节点便升级为存储目录项记录的页。

这个过程特别注意的是:一个B+树索引的根节点自诞生之日起,便不会再移动。这样只要我们对某个表建立索引,那么它的根节点的页号便会被记录到某个地方,然后凡是InnoDB 存储引擎需要用到这个索号的时候,都会从那个固定的地方取出根节点的页号,从而来访问这个索引。

前面我们创建b+树的时候,是从下往上创建的,但实际上在数据库中是自上而下创建的:当你创建完一张表后,在底层就会创建一个根页面,然后你往表中添加了三条记录,这三条记录就放在了根页面中,假如一个页面只能放三条记录,那么当你添加第四条记录时,会再创建一个新页面,把原来的三条数据放在新页面中,然后会再创建一个新页面插入第四条记录(现在有两个新页面了),然后根页面充当目录页。当目录页中的数据放不下了(即现在目录页中的数据放满了,但是我还要再放一条,为了方便解释,把这个目录页称为旧目录页),他会创建一个新的页面,把旧目录页的数据复制过来,然后再创建一个新的页面,插入新的数据(此时创建了两个新页面,为了方便解释,把这两个页成为两个新创建的目录页),然后旧目录页成为这两个新创建的目录页的目录页

内节点中目录项记录的唯一性

一、内节点:非叶子结点,也就是目录页
二、我们前面讲二级索引的目录页中存储的事索引列(要查询的列)+页号,但这样其实不严谨,实际上二级索引在非叶子结点(目录页)中保留了主键的值
三、假如我们前面讲的那张表中的数据是这样:
请添加图片描述
目录页中存储的事索引列(要查询的列)+页号,那么b+树应该长这样:
请添加图片描述
那么问题来了:如果我想插入一条数据:c1=9,c2=1,c3='c',由于页3中存储的目录项记录是由C2列+页号的值构成的,页3中的两条日录项记录对应的c2列的值都是 1,而我们新插入的这条记录的c2列的值也是1,那这条新插入的记录到底应该放到页4中,还是应该放到页5中?
为了让新插入记录能找到自己在那个页里,我们需要保证在B+树的同一层内节点的目录项记录除页号字段以外是唯一的,那我们就需要把主键值也添加到二级索引节点中的目录项记录了。所以对于二级索引的内节点的目录项记录的内容实际上是由三个部分构成的:

  1. 索引列的值
  2. 主键值
  3. 页号

所以我们为c2列建立二级索引后的示意图实际上应该是这样子的:
请添加图片描述
这样,我们就可以先把新插入的数据的c2列的值和页3 中各目录项记录的c2列的值作比较,如果相同,可以接着比较主键值,显然主键值9>7,新记录应该被插入到页5中。

一个页面最少存储2条记录

存两条数据才能构成二叉树,提高搜索效率。
一个B+树只需要很少的层级就可以轻松存储数亿条记录,查询速度相当不错!这是因为B+树本质上就是一个大的多层级目录,每经过一个目录时都会过滤掉许多无效的子目录,直到最后访问到存储真实数据的目录。那如果一个大的目录中只存放一个子目录,那么目录层级非常非常非常多,而且最后的那个存放真实数据的目录中只能存放一条记录。费了半天劲只能存放一条真实的用户记录?所以InnoDB的一个数据页至少可以存放两条记录。

MyISAM中的索引方案

一、b+树索引适用的存储引擎:MyISAM、InnoDB、Memory。即使多个存储引擎支持同一种类型的索引,但是他们的实现原理也是不同的。Innodb和MyISAM默认的索引是B+tree索引,而Memory默认的索引是Hash索引。
二、MyISAM引擎使用B+Tree作为索引结构,叶子节点的data域存放的是 数据记录的地址
三、MyISAM中没有聚簇索引,他里面的索引可以全部理解为二级索引,因为数据和索引是分离的。

MyISAM索引的原理

我们知道InnoDB中索引即数据,也就是聚簇索引的那棵B+树的叶子节点中已经把所有完整的用户记录都包含了,在底层都放在.ibd文件中,而MyISAM的索引方案虽然也使用树形结构,但是却将素引和数据分开存储:

  1. 将表中的记录按照记录的插入顺序单独存储在一个文件中,称之为数据文件,在底层就是.myd文件。这个文件并不划分为若干个数据页,有多少记录就往这个文件中塞多少记录就成了。由于在插入数据的时候并没有刻意按照主键大小排序,所以我们并不能在这些数据上使用二分法进行查找。
  2. 使用MyISAM存储引擎的表会把索引信息另外存储到一个称为索引文件 的文件中,在底层就是.myi文件。MyISAM会单独为表的主键创建一个索引,只不过在索引的叶子节点中存储的不是完整的用户记录,而是主键值+数据记录地址的组合。
    请添加图片描述
    这里设表一共有三列,假设我们以col1为主键,上图是一个MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主键索引和二级素引(Secondary key)在结构上没有任何区别,只是主键索引l要求key是唯一的,而二级索引的key可以重复。如果我们在Col2上建立一个二级索引,则此索引的结构如下图所示:
    请添加图片描述
    同样也是一棵B+Tree,data域保存数据记录的地址。因此,MyISAM中索引检索的算法为:首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。

MyISAM 与 InnoDB对比

MyISAM的索引方式都是“非聚簇”的,与InnoDB包含1个聚簇索引是不同的。小结两种引擎中索引的区别:

  1. 在InnoDB存储引擎中,我们只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录,而在MyISAM中却需要进行一次回表操作(查询数据表获得数据的地址,然后去该地址取数据),意味着MyISAM中建立的索引相当于全部都是二级索引 。
  2. InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。
  3. InnoDB的非聚簇索引data域存储相应记录主键的值 ,而MyISAM索引记录的是地址。换句话说,InnoDB的所有非聚簇索引都引用主键作为data域。
  4. MyISAM的回表操作是十分快速的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。
  5. InnoDB要求表必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。

正确使用和优化索引:
举例1:InnoDB为什么不建议使用过长的字段作为主键:因为所有二级索引都引用主键索引,过长的主键索引会令二级索引变得过大。也就是说,一个数据页默认存放16kb的数据,如果主键的字段过长,那么数据页中存放的数据就少了,b+树的层级就高了
举例2:用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一棵BtTree,非单调的主键会造成在插入新记录时,数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用 自增字段作为主键则是一个很好的选择。

索引的代价

一、空间上的代价:每建立一个索引都要为它建立一棵B+树,每一棵B+树的每一个节点都是一个数据页,一个页默认会占用16KB的存储空间,一棵很大的B+树由许多数据页组成,那在底层就会占用很大的一片存储空间。
二、时间上的代价:每次对表中的数据进行增删改操作时,都需要去修改各个B+树索引。而且我们讲过,B+树每层节点都是按照索引列的值组成了双向链表。不论是叶子节点中的记录,还是内节点中的记录(也就是不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序形成了一个单向链表。而增、删、改操作可能会对节点和记录的排序造成破坏,所以存储引擎需要额外的时间进行一些记录移位, 页面分裂、页面回收等操作来维护好节点和记录的排序。如果我们建了许多索引,每个索引对应的B+树都要进行相关的维护操作,会给性能拖后腿。

一个表上索引建的越多,就会占用越多的存储空间,在增删改记录的时候性能就越差。

MySQL数据结构选择的合理性

猜你喜欢

转载自blog.csdn.net/CaraYQ/article/details/128552789