+----+---------+------+
| id | name | age |
+----+---------+------+
| 1 | 帅哥1 | 30 |
| 2 | 帅哥2 | 18 |
| 3 | 帅哥3 | 25 |
| 4 | 帅哥4 | 21 |
| 5 | 帅哥5 | 29 |
| 6 | 帅哥6 | 35 |
+----+---------+------+
上表中,如果要寻找到id为6的数据,最差的情况则是进行6次IO操作,才能获取到数据。
若是使用二叉树的数据结构进行存储,则只需要3次IO即可获取到数据。
(不使用索引,也有可能一次就会获取到数据,所以在数据量少的情况下,不建议使用索引,且使用索引是需要额外使用存储空间的,对增删改也会造成性能影响)
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
索引的本质:索引是数据结构。你可以简单理解为“排好序的快速查找数据结构”,满足特定查找算法。这些数据结构以某种方式指向数据, 这样就可以在这些数据结构的基础上实现 高级查找算法 。
优点:
缺点:
CREATE TABLE index_demo(
c1 INT,
c2 INT,
c3 CHAR(1),
PRIMARY KEY(c1)
) ROW_FORMAT = Compact;
把记录放到页中如下图所示
接下来以一个例子进行演示
CREATE TABLE index_demo(
c1 INT,
c2 INT,
c3 CHAR(1),
PRIMARY KEY(c1)
) ROW_FORMAT = Compact;
insert into index_demo values(1,4,'u'),(3,9,'d'),(5,3,'y')
这三条数据按照上图的存储结构存储如下图所示
insert into index_demo values(4,4,'a')
我们目前假设一页存储3条数据(但是实际上真正的存储远不止如此),页10已经存满数据了,所以此时不得不再次分配一页来存储 (4,4,‘a’) 这条数据
注意:因为新分配的数据页编号可能并不是连续的。他们只是通过维护着上一页和下一页的编号而建立了链表关系,另外,页10中最大值为5,页28中有一条数据为4,因为5>4,所以这并不符合下一页的数据主键最小值值必须大于上一页的主键最大值的要求,所以再插入(4,4,‘a’) 这条数据时需要伴随着一次记录移动,把主键值为5的记录移动到页28中,主键值为4的记录移动到页10中
页分裂:
这表明了在对页的记录进行增删改操作时,我们必须通过一些诸如记录移动的操作来始终保持上述状态一直成立
(下一页的数据主键最小值值必须大于上一页的主键最大值)这个过程我们成为页分裂,例如当前页存满了,再次分配一个新的页进行存储,这也称为页分裂。
由于数据页的编号可能是不连续的,所以在向index_demo表插入很多记录后,如下图所示
因为这些数据页在物理存储上不是连续的,所以如果想在这些页中根据主键值快速定位某条记录所在页数,我们需要给其做一个目录,每个页对应一个目录,每个目录包括下面两个部分:
例如查找主键为20的记录,可以直接定位到在目录项3,页9上,因为(12<20<209),然后进入页9去寻找主键为20的记录。
上述就是数据页的简单目录(索引)
上述情况中,如果页的数量很大,则对应的目录项也会很大,那么我们也可以将目录项的一条条记录构建为页,我们可以将目录项构建的页称为目录页,数据构成的页成为数据页,那么目录页和数据页如何区分呢?上述讲解到了行记录的概念,是通过record_type进行区分的。
record_type :记录头信息的一项属性,表示记录的类型, 0 表示普通记录、 2 表示最小记录、 3 表示最大记录、 1目录项记录
优化后如下图:
上图中,目录页和数据页的不同处:
相同点:
现在以查找主键为 20 的记录为例,根据某个主键值去查找记录的步骤就可以大致拆分成下边两步:
现在因为存储目录项记录的页不止一个,所以如果我们想根据主键值查找一条用户记录大致需要3个步骤,以查找主键值为 20 的记录为例:
所以我们可以针对现有的目录页再给它创建一层目录页,如下图
如图,我们生成了一个存储更高级目录项的 页33 ,这个页中的两条记录分别代表页30和页32,如果用户记录的主键值在 [1, 320) 之间,则到页30中查找更详细的目录项记录,如果主键值 不小于320 的话,就到页32中查找更详细的目录项记录。
一个B+树的节点其实可以分成好多层,规定最下边的那层,也就是存放我们用户记录的那层为第 0 层,之后依次往上加。之前我们做了一个非常极端的假设:存放用户记录的页 最多存放3条记录 ,存放目录项记录的页 最多存放4条记录 。其实真实环境中一个页存放的记录数量是非常大的,假设所有存放用户记录的叶子节点代表的数据页可以存放 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层 ,那我们通过主键值去查找某条记录最多只需要做4个页面内的查找(查找3个目录项页和一个用户记录页),又因为在每个页面内有所谓的 Page Directory (页目录),所以在页面内也可以通过 二分法 实现快速定位记录。
一页16KB(叶子节点存放100条,则1条数据160B,若一条数据量大的情况,则只是叶子节点存放的数据个数小,目录页的存储数量还是不变)
索引按照物理实现方式,索引可以分为 2 种:聚簇(聚集)和非聚簇(非聚集)索引。我们也把非聚集索引称为二级索引或者辅助索引。
特点:
优点:
缺点:
限制:
这是我们的表结构
CREATE TABLE `index_demo` (
`c1` int(11) NOT NULL,
`c2` int(11) DEFAULT NULL,
`c3` char(1) DEFAULT NULL,
PRIMARY KEY (`c1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
如果我们想要使用别的字段进行查找该怎么办呢?例如c2,我们可以利用c2来建立索引,这就称为二级索引(辅助索引、非聚簇索引)
注意观察此时的叶子节点,存放的数据不是完整的数据,只存放了c2的值和主键的值(非聚簇索引的叶子节点是不存放隐藏列的)。(没有存放c3哦)
回表:我们根据这个以c2列大小排序的B+树只能确定我们要查找记录的主键值,所以如果我们想根据c2列的值查找到完整的用户记录的话,仍然需要到 聚簇索引 中再查一遍,这个过程称为 回表 。也就是根据c2列的值查询一条完整的用户记录需要使用到 2 棵B+树
因为c2不是主键,如果为c2创建索引,它是非聚簇索引,是不存放具体的每一行的数据的,所以根据c2构建的B+树寻找到对应的数据的主键,还得去聚簇索引构建的B+树去根据非聚簇索引构建的B+树得到的主键值去寻找对应的数据,相当于进行了两个B+树的遍历。
到这里也清楚了聚簇索引和非聚簇索引的区别以及优缺点。
我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按照 c2和c3列 的大小进行排序,这个包含两层含义:
注意一点,以c2和c3列的大小为排序规则建立的B+树称为 联合索引 ,本质上也是一个二级索引。它的意思与分别为c2和c3列分别建立索引的表述是不同的,不同点如下:
在介绍B+树索引时,当时是先有数据页再有目录页的,但是实际上的不是这样,真实的情况如下
注意:一个B+树的索引的根节点自诞生起便不会移动,我们对表创建索引时,他的根节点的页号会被记录,后续InnoDB存储引擎需要用到索引时,直接取出根节点的页号进行查询即可。
MyISAM的索引方案默认也是B+Tree,但是是将数据与索引分开存放。
MyISAM的索引方式都是“非聚簇”的,与InnoDB包含1个聚簇索引是不同的。小结两种引擎中索引的区别: