局部性原理
要想理解 mysql 索引的底层原理,必须知道一个前置知识,那就是局部性原理
用户要想读取磁盘上的数据,必须经过磁盘的 IO 。众所周知,IO 读取磁盘数据是非常耗时的,为了提高读取效率,就必须要减少 IO 次数。因此,磁盘读取数据并不是按需读取,而是每次都会预读一部分数据,即使只需一个字节,磁盘也会从这个字节开始,往后读取一定长度的数据放入内存
后面读取到的这部分数据并不是多余的,因为经过科学家实验验证,一个数据被用到时,其附近的数据通常也会被用到,这就是局部性原理
由于磁盘的顺序读取效率很高(对于机械硬盘,一旦找到数据后,其附近的数据读取不需要寻道,只需很少的旋转时间),因此对于局部性原理的程序来说,磁盘预读能提高 IO 效率。因此操作系统读取磁盘都是以页为单位读取的,一页的大小通常为 4k,一次 IO 操作一般读取页的整数倍数据
索引为什么不用 Hash 存储
提到 Hash,学过 java 的人马上就会联想到 HashMap。jdk1.8 中 HashMap 底层通过 **Hash 表 + 链表 + 红黑树 **进行存储,对于等值查寻,使用这种方式会非常快,如下所示
select * from user where name='zhangsan';
只要把字符串 zhangsan 进行 hash 操作,然后在链表和红黑树中进行查找,效率很高,然而它是基于内存存储的
**
但是,索引本身也很大,不可能全部存储在内存中,而且一般数据库都是范围查找,由于 hash 不保证数据的有序性,因此 hash 就不太适合了
索引为什么不用二叉树、红黑树进行存储
由于索引本身很大,因此索引往往以索引文件的形式存储在磁盘中,这样的话,索引的查找就会产生磁盘 IO 的消耗,比较内存查找,磁盘 IO 的消耗要高上几个数量级,所以评价一个数据结构存储索引的优劣的重要指标就是查找过程中磁盘 IO 的次数
使用二叉树、红黑树这种数据结构存储到磁盘上,逻辑上很近的节点,实际上在磁盘中可能会相隔很远,无法利用到局部性原理,所以读取一个节点就需要一次 IO。使用红黑树存储数据,仅仅很少的数据就会使树的深度过大,这样就会需要很多次的 IO,因此这种数据结构也不适合
为什么不用 B 树存储索引
上图就是 B 树的数据结构图,和二叉树、红黑树一样,读取 B 树一个节点需要一次磁盘 IO,但是,B 树一个节点中能存多条数据,这多条数据可以放在一块连续的磁盘区域,可以利用局部性原理
一般操作系统一次性会读取 4 页的磁盘数据,也就是 16k 的数据,为了充分利用局部性原理,一个 B 树的节点大小会也会被设置成 16k,因此一个节点被叫做一个磁盘块
因为一个 B 树的节点不仅存指针,还存 key 和数据,这样就会导致一个磁盘块中存放不了太多的指针和节点,也会使树的深度变得很大,导致 IO 次数过多,因此这种数据结构也不适合
B+ 树的优势
B + Tree 是在 B Tree 之上进行的优化,优化如下:
- 非叶子节点不存储数据,这样做的好处是非叶子节点中能存储更多的指针和 key,这样会将数据分隔成更多的数据区间,降低了树的深度,也加快了检索的速度
- 叶子节点磁盘块之间建立了双向链表,符合磁盘的预读特性,更适合范围、分页查找
- 假设一个 key 和指针占 10b 大小,一个磁盘块大小 16k,那么一个磁盘块就能存储 1600 个key 和指针,两次 IO 就能索引 1600 * 1600 数据,因此通过三次 IO 就能索引百万数据,效率非常高
InnoDB 索引实现
InnoDB 表数据文件本身就是以 B+ 树组织的一个索引文件,索引的 key 就是表的主键,因此这种索引也就聚集索引。
索引 InnoDB 要求表必须要有主键,如果表没有主键,则会把唯一字段作为表的主键,如果表没有唯一字段,就会自动生成一个隐含字段作为主键,这个字段长度位 6 字节,类型为长整形
如果是普通索引,那么叶子节点存储的是主键的值,而不是整条记录,因此建立普通索引后,查找数据对应的需要经过两次索引,第一次是找到记录的主键值,第二次是根据主键值查找数据。因此主键不能过大,因为主键大,索引就大
MyISAM 索引实现
MyISAM 存储引擎也是使用 B+ 树来存储索引,但是它的索引文件和数据是分开的,索引中叶子节点存的是实际数据的地址,因此这类索引叫做非聚集索引
在 MyISAM 中,主键索引和普通索引在存储结构上没有任何区别,只是主键索引要求主键唯一,而普通索引可以重复
MyISAM 和 InnoDB 比较
| MyISAM | InnoDB | |
|---|---|---|
| 事务 | 不支持 | 支持 |
| 表锁 | 支持 | 支持 |
| 行锁 | 不支持 | 支持 |
| 外键 | 不支持 | 支持 |
| 全文索引 | 支持 | 支持(5.6后支持) |
| 使用场景 | 大量 select | 大量 insert、delete、update |
因为 MyISAM 管理非事务表,支持高速存储和全文检索,适合大量查询场景;而 InnoDB 支持事务,适合执行大量的 insert、update、delete,提高并发时的性能