首页 > 技术文章 > 数据库-索引

ijay 2021-03-17 16:36 原文

数据库索引几乎是优化的重点。了解原理也就可以直接理解之前为什么这样做优化,比如为啥索引是左倾的,为啥命中主键比命中其他索引效率要高。
面试被问到的最多的也就是优化,有少数问到索引实现原理的,虽然面试命中比较少,但是如果回答上肯定是大大加分的。

innodb与myisam的区别
事务:innodb支持,myisam不支持事务。
锁:innodb支持行级锁,myisam仅支持表级锁。
存储:innodb索引与数据存储在一起,myisam索引单独存在一个文件中。
外键:innodb支持外键,myisam不支持外键。
查询速度:myisam比innodb快。
可见myisam缺点更突出,优点也不是很明显。所以在mysql8版本开始弃用该引擎,后面的知识也不再考虑该引擎。

btree索引的原理,聚集索引与非聚集索引
首先,BTree索引使用的是B+树结构,B+树是一种平衡搜索数特性如下:
1.关键字数和子树相同
2.非叶子节点仅用作索引,它的关键字和子节点有重复元素
3.叶子节点用指针连在一起
以上三点特性,导致它很适合做索引,每个节点存放的是一页索引信息,一页可以包含很多索引,这样树的深度就缩短减少了磁盘读写,而且是搜索树,节点存储的元素是有序的,叶子节点连接在一起有助于排序和范围查找。
然后,Mysql中BTree索引存储的结构,非叶子节点存储的页中存储索引信息,例如使用col1、col2、col3做的符合索引,则根绝三个字段的值做成索引信息,并且根绝这个三个字段顺序进行排序。叶子节点除了存储索引还存储数据信息,若是主键索引数据信息就是数据行即为聚集索引,若非主键索引存储的信息为指向数据行的指针,即为非聚集索引。
最后,BTree索引的查找和添加。
查询数据:
1.匹配索引,根据索引查找数据的时候,根据sql条件自己来匹配索引,使用最左原则筛选。
2.根据匹配的索引在BTree中从根节点一层一层查找,由于B+树深度比较低且有序,所以查找次数较少就可以找到结果。
3.若为相当或like查询,则在每一层匹配索引内容,遍历所有节点,若该节点都没有对应内容且内容不再该节点范围内,则不再查找子节点,若有该内容或者在此范围内则向下一层查找知道叶子节点即可。
4.若为范围查询,首先明确范围左右区间,根据左右区间遍历查找节点,若在范围内的节点查找其子节点,否则无需查找。直到叶子节点。由于叶子节点有序且根绝顺序相连接的,所以查找范围可以直接根据筛选后的叶子节点顺序读取数据。
添加数据:当要插入新数据的时候根据主键索引查找到应该插入位置并进行插入,有可能需要进行分裂操作,不过B+树结构的分裂只会影响前一节点的指针和本节点,不会影响其他节点,这样可以保证效率和稳定性。主键索引和数据插入好只有再更新其他非主键索引信息。
其实该问题回答前面两步既可以。这样了解了btree索引的原理后可以多解释两点其他的问题:
1.为什么主键索引查找效率更高?因为主键索引是聚集索引,根据索引查找到叶子节点的时候就直接查找到数据行,而非主键索引查找到叶子节点后还需要根据指针去主键索引中提取数据行,多了一步所以也就效率低。
2.索引最左原则。举个例子:
索引为col1、col2、col3,由于制作索引是按照这三个字段值并且是这个字段顺序来做,比如有三行数据(1,0,0)(1,1,0)(1,1,1),简单看做100,110,111而且也是按照这个顺序存储的。
那么若匹配col1=1,col3=1如何匹配呢,那么就是101到191之间都算。而使用最左原则匹配col1=1,col2=1的时候,值需要匹配110到119之间的元素即可。一个匹配90次,一个要匹配9次
当中间在多两个字段col1、col2、col4、col5、col3,那么结果是什么样的?那就是10001到19991之间的字段都算。那么搜索量是指数型增长的。而使用最左原则匹配col1=1,col2=1那么匹配到11000到11999即可。一个要匹配9990次一个要匹配999次
看到了吧,匹配的次数是指数型增长的。而这是简单理解的方式,其实使用了平衡搜索树实现的话,满足最左原则匹配的次数远比上面列举的9次、999次少很多,而不满足最左原则的话匹配次数确实没有啥变化。这个数量级几乎和全表扫相差的不多(因为数树状结构,还要按照层级扫描,而全表扫描只扫最后一层而不用去遍历每一层里其他节点,是不是相差的就小了)。
所以建立mysql索引最左原则,简单理解,符合索引从左到右的字段顺序进行匹配,知道匹配不到为止,例如建立索引为col1、col2、col3,可以匹配的索引为 col1、col2、col3;col1、col2;col1。

索引常用方法与区别
常用的索引为Btree和hash,前面介绍过Btree。
hash索引就是计算索引的hash值来进行匹配,类似于有一个HashMap一样。
但是hash索引很少被用到,主要原因就是它的限制比较多。
1.只能用于=、IN、<>的条件,不能用于范围查询。
2.要么全部命中,要么全部不命中,不提供BTree那种最左前缀查询的便利
3.无法避免表扫描,因为hash所以不提供聚集索引。
4.若索引字段值的区别不大,例如性别字段,那么索引效率很低。
5.由于hash值无序,所以hash索引不能优化排序。
由于这么多的限制,至今我也没有用到过它,但是它也并非一无是处,首先索引简单维护速度也快,其次单行定位查找很快,所以它用在单行定位查找的表。

索引优化
首先,如何定位要优化的语句,定位优化点大家第一反应就是慢查日志,其实这个解决问题使用的,最开始设计表的时候就应该想好该表将来要怎么去查询,定好主键和索引。做压力测试或者线上问题查找的时候可以开启慢查来定位运行比较慢的语句。
其次,优化工具,定位到运行比较慢的语句,优化工具无非就是explain了。查看语句是否命中索引,不命中索引的几个重要原因:1.没有创建索引(这个问题就太2了);2.索引最左原则;3.条件使用or语句和like带有左%是不命中索引的,另外in子句条件过多也不命中索引,4.数据类型不匹配,即使用sql的隐式转换不命中索引。5.mysql自己评估若全表扫描更划算就不命中索引,in子句不命中索引主要就是因为这个。
再次,防止过度优化。这个就是经常问的是否是索引越多越好。这个是分情况的,若是数据定义表,数据行几乎不变,那索引数量多少都可以。但若是个数据表,数据行经常变动,每修改一次数据行,都要维护所有的索引的,这个开销巨大。索引优化了查询,但是给增删改带来的是负面影响。

若有一张表数据量巨大,现在要添加一个字段,或者要添加一个索引,要如何做?
这个是以前被问到的一个问题。没有管理过线上数据的同学可能有点质疑,为啥这么问。
首先,线上数量大的表会有不少的。增加字段或者创建索引都是一个不容易的事,因为这一个操作会使数据库负载突然增加很大,导致整个数据库卡顿。所以在线操作是很危险的事。
其次,也是在问你怎么避免这么干,若非干不可要怎么办。
所以这里可以按照这个思路答复:首先评估是否要添加到原来的大表上,若非必要,可以建立一个大表的分表来维护这个数据,也可以防止表变得更大。若非加不可那就得尽量避免在线加字段,要考虑在维护时间增加。若紧急事件非加不可那就需要进行监控数据库压力,分批增加,而不能同实例机上所有数据库一起加。

参考资料
MySQL的InnoDB索引原理详解
MySQL索引-B+树
mysql Hash索引和BTree索引区别
mysql中innodb和myisam对比及索引原理区别

推荐阅读