索引
概念
官方话:索引是帮助MySQL高效获取数据的数据结构。
- 为了能够使存储引擎快速找到数据记录的一种数据结构,就相当于是一本书的目录部分,目的是为了减少磁盘的IO次数,避免了全表扫描,加快查询速率。
- 索引是在存储引擎中实现的,InnoDB底层索引使用的是B+Tree的数据结构,不同的存储引擎使用的数据结构可能不一样。同时存储引擎可以定义每个表的最大索引数和最大索引长度。
优点
- 降低数据库的IO成本。
- 保证数据库表中的每一行的唯一性。
- 在实现数据的参考完整性方面,可以加速表和表之间的连接。对有依赖关系的子表和父表联合查询时,可以提高查询速度。
- 在使用分组和排序子句进行查询时,可以显著的减少分组和排序的时间,降低了CPU的消耗。
- 因为索引本质上就是一种排好序的快速查找数据结构,就能提高分组和排序效率。
缺点
- 创建索引和维护索引需要耗费时间,数据量越多,消耗时间越多。
- 索引需要占用磁盘空间,存储在磁盘上。
- 索引虽然提高了查询速度,但是降低了更新表的速度,因为更新表和删除需要动态更新维护索引,就降低了维护速度。
- 解决办法就是,更新之前直接全部删了,更新表之后再重新创建索引,虽然会消耗时间,但是效率会比动态维护索引要高一点。
InnoDB索引推演
数据页:存储引擎从磁盘中读取数据是以数据页为单位进行读取的,一个数据页能存放16kb大小的数据。
精确匹配例子:
SELECT [列名列表] FROM 表名 WHERE 列名 = XXX;
在一个页中的查找
目前表中数据量较小,所有记录可以被存在一个页中,就可以根据搜索条件的不同分为两种情况:
- 以主键为搜索条件
- 使用二分法快速找到对应记录,因为主键一般是自增有序的。
- 以其它列为搜索条件
- 用其他列的话就只能是从最小记录开始依次遍历,效率较低。
在多个页中的查找
假设有很多条数据,存在很多个数据页来记录,此时查找数据就有两个步骤:
- 首先得找到这个数据所在的对应数据页。
- 再从这个数据页中找到想要的数据。
那么问题就来了,如果数据量非常非常大,比如有1000000条数据,那么就会有很多个数据页,而数据页之间是不存在有规律的,无法快速定位到目标数据在哪个数据页上,此时有两种办法:
- 死办法,从第一个数据页挨个遍历,沿着双向链表往下找,遍历所有数据页。
- 这下就可以用到索引了,索引就很好的解决了这个问题。
举个图书馆的例子,每个书架就相当于数据页,而你要找的书就相当于要找的数据,如果没有索引(书架边上的索引号),你就只能一个个书架,一本本书去翻,而有了索引你就能快速去对应书架对应行找到你想要的。
再来看一个简单的索引设计方案,这里要说一下,在磁盘中数据和数据页的存放地址不一定是连续的,是无规则存放的,因此记录和记录之间是采用单链表的数据结构存储,而数据页和数据页之间是采用双向链表的方式存储。
- 每个数据页中的记录主键一定是下一个页的要大于之前页的主键,如果存入的数据不满足这个特点,就会发生数据记录移动,使其按主键排序,这个移动的过程就叫做页分裂。
- 如果我们没有索引,就得一个个数据页一条条记录去遍历,因此,设计一个简单的索引,即目录项。
- 页的记录中最小的主键值,用key表示。
- 页号就用page_no来表示。
- 有了这个目录就可以通过主键的值快速找到对应的数据页,同时也可以对目录项进行二分查找,来取代遍历索引的操作,进一步提高效率。
PS:以上这个设计方案讲的是基于主键的索引设计!
迭代1次:目录项记录的页
由于如果目录项每个记录之间用物理连续的方式存储的话,那么如果更新数据,插入数据、删除数据等操作都会导致后面的数据要往前移动,索性就干脆也用数据页的方式保存目录项,目录记录之间用单链表保存,那么怎么和用户数据区分呢,关键就在于record_type字段,1位目录,0为用户记录。每个数据页都会维护一个页目录,用于二分查找快速找到数据。
迭代2次:多个目录项记录的页
如果目录项记录多到一个页不够用了,那就再生成一个页来保存,就存在了多个页保存目录
迭代3次:目录项记录页的目录页
如果目录项记录页也很多了,找起来效率也比较低了,就可以给这些目录项记录页设置一个目录页,相当于大目录嵌套小目录,小目录里查询对应的数据,能减少IO次数,提高效率。
至此,我们的整个目录页和对应数据的关系就可以用一个数据结构来模拟,这也是B+树的大致样子:
叶子节点就是我们普通用户记录的数据页,非叶子结点就是目录页了。
从图上来看B+树的层次越低,IO的次数也就越少,我们实际开发中的B+树一般不会超过四层,因为四层已经可以存储非常非常多的记录了。
常见索引
索引根据物理实现方式,可以分为2种:聚簇索引和非聚簇索引(二级索引、辅助索引),也就是以是否以主键为标准构建来区分。
聚簇索引
聚簇索引不是一种单独的索引类型,而是一种数据存储方式,也就是所谓的索引即数据数据,数据即索引,上面所介绍的B+树都是聚簇索引。
另外,我们不需要再MySQL语句中显式的使用INDEX去创建聚簇索引,InnoDB会自动为我们创建。
优点
- 访问数据更快,因为数据就在索引中。
- 对于主键的排序查找和范围查找速度快。
- 节省了大量的IO操作。
缺点
- 插入速度严重依赖于插入顺序,如果插入不按照主键的顺序插入的话,容易出现页分裂,影响性能,因此InnoDB中一般都会设置一个自增的主键。
- 更新主键代价很高,会导致被更新的行移动,因此主键一般不可更新。
- 二级索引访问需要两次索引查找,第一次找主键,第二次根据主键查找行数据。
限制
- 目前只有InnoDB中有聚簇索引,其它不支持。
- 每个表只能用一个聚簇索引,一般就是主键。
- 没有定义主键的话就会选择非空的唯一索引代替,还没有的话就会定义一个隐式的主键。
- 尽量选择有序的顺序id作为主键。
二级索引
聚簇索引是根据主键进行搜索的,那如果我们的搜索条件不是主键而是其它的字段列呢?
此时就需要以其它的字段列再构建一颗B+树,也就是二级索引。
整体上和聚簇索引长得差不多,主要有以下区别:
- 二级索引是以非主键列名为序构建的B+树。
- 叶子结点存放的不是数据,而是数据的位置。
- 如果需要返回的字段不止是搜索条件这一个字段,就需要根据查出来的主键进行回表操作,到聚簇索引中继续查询其他字段。
- 非聚簇索引是可以有多个的,聚簇索引只能有一个。
- 插入删除更新的效率非聚簇索引效率更高。
联合索引
也就是以多个列的字段为搜索条件构建的索引,称为联合索引。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cA8zq7oe-1676467383581)(null)]
先以第一个列的字段排序,再依次以后面的字段排序,构成一颗B+树,本质上是一个二级索引。
InnoDB中B+树索引的注意事项
- 根页面的位置万年不动
- 这是什么意思呢,上面介绍的B+树为了方便理解是由下往上依次创建的,而实际上B+树的生成不是这样的。
- 首先会有一个根节点,用来存放所有的用户记录,如果当前页存不下这么多数据了,就会新开一个页(子节点),将当前节点的数据存入子节点,根节点变为目录页,目录页一个不够用的时候,又会生成新的目录页子节点,同样复制过去,根节点变为目录页的目录页,以此类推,根节点永不会变。
- 内节点中目录项记录的唯一性
- 非叶子结点如果采用的是索引列+页号的搭配的话,如果多条记录列的值相同,就不能判断该插入哪个页了,因此就会选择索引列+主键+页号的方式进行唯一性保证。
- 一个页面最少存储2条记录