前言
索引本质是一种加速访问的数据结构,可以类比成一本书的目录;查找数据时,先从目录中找到对应章节的页码,再通过页码找到目标内容。本文将带领大家搞清楚以下几个问题:索引是什么、有什么作用、MySQL索引数据结构、索引创建和优化的原则,索引失效的场景。
索引是什么?
官方介绍索引是帮助MySQL高效获取数据的数据结构。更通俗的说,数据库索引好比是一本书前面的目 录,能加快数据库的查询速度。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往是存储在磁盘上的文件中的(可能 存储在单独的索引文件中,也可能和数据一起存储在数据文件中)。
索引分类
按数据结构分类
- B+树索引:这是MySQL中最常见的索引类型,适用于全键值、键值范围和最左前缀查找。B+树索引在非叶子节点上仅存储键值,所有数据记录均存储在叶子节点上,并且数据是按照顺序排列的
- 哈希索引:基于哈希算法实现,适用于精确匹配查询,不支持范围查询。哈希索引的检索效率非常高,但由于其特殊性,也带来了一些限制和弊端。
- 全文索引:用于查找文本中的关键词,通常使用倒排索引实现。
按物理存储分类
- 聚集索引:按照每张表的主键构造一颗B+树,叶子节点存放的是整张表的行记录数据。InnoDB表要求必须有聚集索引
- 非聚集索引:叶子节点存放的是数据主键,而不是行数据。使用非聚集索引进行查找时,需要先查找到主键值,然后再到聚集索引中进行查找
按字段特性分类
- 主键索引:建立在主键上的索引,一张数据表只能有一个主键索引,索引列值不允许有空值
- 唯一索引:建立在UNIQUE字段上的索引,索引列值允许为空,列值中出现多个空值不会发生重复冲突
- 普通索引:建立在普通字段上的索引
- 全文索引:用于查找文本中的关键词
按字段个数分类
- 单列索引:建立在单个列上的索引
- 联合索引(复合索引、组合索引) :建立在多个列上的索引,使用联合索引时要遵循最左前缀匹配原则
索引优势和劣势
优势
- 可以提高数据检索的效率,降低数据库的IO成本,类似于书的目录。
- 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。
- 被索引的列会自动进行排序,包括【单列索引】和【组合索引】,只是组合索引的排序要复杂 一些。
- 如果按照索引列的顺序进行排序,对应order by语句来说,效率就会提高很多。
劣势
- 索引会占据磁盘空间。
- 索引虽然会提高查询效率,但是会降低更新表的效率。比如每次对表进行增删改操作,MySQL不仅 要保存数据,还要维护索引文件。
索引数据结构
基本需求
索引的数据结构,至少需要支持两种最常用的查询需求:
- 等值查询:根据某个值查找数据,比如: select * from t_user where age=76;
- 范围查询:根据某个范围区间查找数据,比如: select * from t_user where age>=76 and age<=86;
- 排序
- 分组
同时需要考虑时间和空间因素:
- 性价比高 在执行时间方面,我们希望通过索引,查询数据的时间尽可能小。
- 在存储空间方面,我们希望索引不要消耗太多的内存空间和磁盘空间。
常见数据结构
Hash table
Hash表,常见的数据结构之一。 我们使用Hash表存储表数据Key可以存储索引列,Value可以存储行记录或者行磁盘地址。Hash表在等 值查询时效率很高,时间复杂度为O(1); 但是不支持范围快速查找,范围查找时还是只能通过扫描全表方式。
使用场景: 对查询并发要求很高,K/V内存数据库,缓存。
二叉查找树
二叉树特点:每个节点最多有2个分叉,左子树和右子树数据顺序左小右大。
二叉树的检索复杂度和树高相关:理想状态下效率可以达到O(logn)
极端情况下,二叉查找树会构建成为单向链表 = 查找全表扫描。 对磁盘不友好(一旦变成了全表扫描,磁盘io将是极其沉重)。
红黑树
平衡二叉树是采用二分法思维,平衡二叉查找树除了具备二叉树的特点,最主要的特征是树的左右两个子树的层级最多相差1。在插入删除数据时通过左旋/右旋操作保持二叉树的平衡,不会出现左子树很 高、右子树很矮的情况,避免了二叉查找树退化成链表的情况。但是由于红黑树是二叉树,数据量较大时,树的高度变大,导致查询数据时,需要随机访问多次才能找到数据。
使用平衡二叉查找树查询的性能接近于二分查找法,时间复杂度是 O(log2n)。
平衡二叉树存在的问题 :
时间复杂度和树高相关:树有多高就需要检索多少次,每个节点的读取,都对应一次磁盘 IO 操作 【瓶颈】。 磁盘每次寻道时间为10ms,在表数据量大时,对响应时间要求高的场景下,查询性能就会出 现瓶颈。 举例:1百万的数据量,log2n约等于20次磁盘IO,时间20*10=0.2s 。
B树:改进二叉树,为多叉树
想要减少耗时的IO操作,就要尽量降低树的高度。每个节点存储多个元素,在每个节点尽可能多的存储 数据。每个节点可以存储1000个索引(16k/16=1000),这样就将二叉树改造成了多叉树,通过增加树的叉树,将树从高瘦变为矮胖。
主要特点:
- B树的节点中存储着多个元素,每个节点内有多个分叉。
- 节点中的元素包含键值和数据,节点中的键值从大到小排列。也就是说,在所有的节点都储存数据。
- 父节点当中的元素不会出现在子节点中。
- 所有的叶子结点都位于同一层,叶节点具有相同的深度,叶节点之间没有指针连接。
优点:
- 磁盘IO次数会大大减少。
- 比较是在内存中进行的,比较的耗时可以忽略不计。
- B树的高度相比于平衡二叉树会大幅缩小,所以使用B树构建索引可以很好的提升查询的效率。
缺点:
- B树不支持范围查询的快速查找:如果我们想要查找15和26之间的数据,查找到15之后,需要回到根节点重新遍历查找,需要从根节点进行多次遍历,查询效率有待提高。
- 空间占用较大:如果data存储的是行记录,行的大小随着列数的增多,所占空间会变大。一个页中可存储的数据量就会变少,树相应就会变高,磁盘IO次数就会变大。
B+树:改进B树,非叶子节点不存储数据
在B树基础上,MySQL在B树的基础上继续改造,使用B+树构建索引。B+树和B树最主要的区别在于非 叶子节点是否存储数据的问题。
- B树:非叶子节点和叶子节点都会存储数据。
- B+树:只有叶子节点才会存储数据,非叶子节点只存储键值。叶子节点之间使用双向指针连接,最 底层的叶子节点形成了一个双向有序链表。
等值查询:假如我们查询值等于15的数据。查询路径磁盘块1->磁盘块2->磁盘块5。
范围查询:假如我们想要查找15和26之间的数据。
- 查找路径是磁盘块1->磁盘块2->磁盘块5。
- 首先查找值等于15的数据,将值等于15的数据缓存到结果集【三次磁盘IO】。
- 查找到15之后,底层的叶子节点是一个有序列表,我们从磁盘块5,键值15开始向后遍历筛选所有符合筛选条件的数据。
- 第四次磁盘IO:根据磁盘5后继指针到磁盘中寻址定位到磁盘块6,将磁盘6加载到内存中,在内存 中从头遍历比较,15<17<26,15<26<=26,将data缓存到结果集。
优点:
- 继承了B树的优点【多叉树的优点】
- 保证等值和范围查询的快速查找
- MySQL的索引就采用了B+树的数据结构
索引创建原则
-
- 表记录很少不需创建索引 :索引是要有存储的开销
-
2.一个表的索引个数不能过多:
- 空间:浪费空间。每个索引都是一个索引树,占据大量的磁盘空间。
- 时间:更新(插入/Delete/Update)变慢。需要更新所有的索引树。
-
3.频繁更新的字段不建议作为索引:频繁更新的字段引发频繁的页分裂和页合并,性能消耗比较高。
-
4.区分度低的字段,不建议建索引。
-
5.在InnoDB存储引擎中,主键索引建议使用自增的长整型,避免使用很长的字段。
- 使用自增的数字类型,是因为B+tree是顺序排列的。
- 主键索引树一个页节点是16K,主键字段越长,一个页可存储的数据量就会越少,比较臃肿,查询时尤其是区间查询时磁盘IO次数会增多。辅助索引树上叶子节点存储的数据是主键值,主键值越长,一个页可存储的数据量就会越少,查询时磁盘IO次数会增多,查询效率会降低。
-
6.不建议用无序的值作为索引:例如身份证、UUID。更新数据时会发生频繁的页分裂,页内数据不紧凑,浪费磁盘空间。
-
7.尽量创建组合索引,而不是单列索引。
- 1个组合索引等同于多个索引效果,节省空间。
- 可以使用覆盖索引
-
8.最左原则:组合索引应该把频繁用到的列、区分度高的值放在前面。频繁使用代表索引的利用率高,区分度高代表筛选粒度大,这样做可最大限度利用索引价值,缩小筛选范围
InnoDB索引介绍
主键索引
每个InnoDB表都有一个主键索引(也叫聚簇索引) ,主键索引使用B+树构建,叶子节点存储的数据是整行记录。当一个表没有创建主键索引时,InnoDB会自动创建一个ROWID字段来构建聚簇索引。
二级索引
除聚簇索引之外的所有索引都称为辅助索引。在中InnoDB,辅助索引中的叶子节点存储的数据都是该行的主键值。 在检索时,InnoDB使用此主键值在聚簇索引中搜索行记录(回表)。
什么是回表?
根据在辅助索引树中获取的主键id,到主键索引树检索数据的过程称为回表查询。
组合索引
组合索引也是二级索引,和单列索引不同,组合索引包含多个字段,支持多条件搜索。
覆盖索引
select中列数据如果可以直接在辅助索引树上全部获取,也就是说索引树已经“覆盖”了我们的查询需求, 这时MySQL就不会白费力气的回表查询,这中现象就是覆盖索引。
索引下推
官方索引条件下推: Index Condition Pushdown,简称ICP。是MySQL5.6对使用索引从表中检索行的 一种优化。ICP可以减少存储引擎必须访问基表的次数以及MySQL服务器必须访问存储引擎的次数。可用于InnoDB和MyISAM表,对于InnoDB表ICP仅用于辅助索引。
在没有使用ICP的情况下,MySQL的查询过程如下:
- 存储引擎读取索引记录。
- 根据索引中的主键值,定位并读取完整的行记录。
- 存储引擎将记录交给服务器层,服务器层检测该记录是否满足WHERE条件。
使用ICP的情况下,查询过程如下:
- 存储引擎读取索引记录(不是完整的行记录)。
- 判断WHERE条件部分能否用索引中的列来做检查,条件不满足则处理下一行索引记录。
- 条件满足,使用索引中的主键定位并读取完整的行记录。
- 存储引擎将记录交给服务器层,服务器层检测该记录是否满足WHERE条件的其余部分。
实践示例
假设有一个用户表tuser,表里创建了联合索引(name, age)。如果需要检索名字第一个字是“张”,且年龄是10岁的所有用户,可以使用以下SQL语句:
SELECT * FROM tuser WHERE name LIKE '张%' AND age = 10;
在没有使用ICP的情况下,存储引擎会根据联合索引的最左匹配原则,遇到范围查询或者模糊查询就停止,因此找到name LIKE '张%' 的主键id,然后逐一进行回表扫描,再由服务器层对数据根据age = 10进行筛选。
使用ICP后,存储引擎会在联合索引里按照age = 10过滤,减少回表次数,从而提高查询效率。
索引失效场景
- 不符合最左前缀匹配原则。
- 不在索引列上做任何操作【计算、函数、类型转换】,会导致索引失效,转而使用全表扫描
- 存储引擎不能使用索引中范围条件右边的列。
- 不等于【!= 或 <>】,索引会失效
- is null,is not null,索引会失效
- like以通配符开头,索引会失效
- 隐式类型转换的:如字符串不加单引号,索引会失效。
- 少用or,用它来连接时,索引会失效
总结
索引在MySQL高性能查询方面非常重要,要用好索引,需要注意以下几点:
- 理解索引的数据结构。
- 掌握索引的创建原则。
- 掌握索引的使用原则:避免索引失效、减少回表次数。