索引对于数据库的性能是非常重要的,尤其是随着数据量越来越大。如果数据量比较少没有索引数据库性能可能表现也还不错,但是随着数据增多,数据库性能下降的的会非常厉害,因此索引对于数据库的重要性是不言而喻的,索引优化可能是提高数据库性能的最佳方式。今天就来学习一下数据库索引方面知识点,这个也是面试必问的问题了,所以重新学习一下还是非常有必要的,毕竟金三银四的季节已经来了。
一、索引基本知识
1、什么是索引
反正每次说到数据库优化大家一般第一个想到的就是索引,但是什么是索引呢?简单的说索引就是数据库系统中的一种数据结构,方便快速查询和更新数据库表中的数据。至于它的工作原理下面再讲。
2、索引类型
这里说的类型指的是索引的数据结构类型。索引的类型有很多,不同的索引类型有着不同的目的。索引实现是在存储引擎这一层级,而不是服务端,这样索引在不同的存储引擎可能会有不同。我们看下mysql数据库都支持哪些类型以及它们的优缺点。
2.1 B-Tree索引
mysql的存储引擎基本都支持B-Tree索引,但是有一点需要理清楚,我们说使用B-Tree索引,但是存储引擎内实现的数据结构可能并不一定就是B-tree,意思索引名称是B-Tree,但是实际使用的数据结构并不是B-tree。比如mysql默认的存储引擎InnoDB,它的数据结构实际上是B+ tree。关于数据结构内容这里就不做介绍了,但是面试的时候也会经常问到B Tree和B+ Tree的区别,建议看看以B tree和B+ tree的区别来分析mysql索引实现。
2.1 hash 索引
hash索引是建立在hash表上的,并且只有精确匹配索引所有列的查询才有效,它是不支持范围查询的。存储引擎对所有的索引列计算出一个hash code,将其存储在索引中,同时哈希表中保存每个数据行的指针。这样,对于此种索引查找速度是非常快的。出现哈希碰撞的话,索引会以链表的形式存放多个记录指针到同一个哈希条目中。在MySQL中只有内存存储引擎(数据存储在内存中,而不是硬盘上)显性支持hash索引。
2.3 Spatial(R-Tree)索引
MyISAM引擎支持这种索引,但是MySQL支持并不好,因此人们一般不使用,而是使用PostgreSQL。
2.4 full-text索引
即全文本索引,以前好像只有MyISAM支持,现在InnoDB也支持全文本索引。全文本索引指的是在文本中查找关键字而不是直接比较索引中的值。全文本索引只能使用在数据类型为 CHAR、VARCHAR和TEXT这三种类型。
除了上面说的这几种,还有一些第三方存储引擎的索引类型,这里就不再做介绍了,感兴趣的可以查阅相关资料。
3、索引的优势
帮助加快数据查询速度只是索引的优点之一,除此之外,以最常用的B-Tree索引为例,还有以下几个好处:
i 减少服务器必须检查数据量;
ii 帮助服务器避免排序和临时表;
iii 将随机I/O转为有序I/O
二、索引策略
1、隔离列
除非在查询中隔离列,否则MySQL通常不能使用列上的索引,“隔离”意思是在语句中,它不能是表达式的一部分或者在一个函数内部。比如:
select * from t_user where id + 1 = 4;
其实这个查询就不会使用到索引,因为查询中使用了表达式“id + 1 = 4”,我们使用explain来对比一下结果:
根据上面的图片可以看出来,表达式查询结果type为ALL,rows为4,这样的查询效率是非常低的。对比第二种查询,type为const,rows为1。之所以有这样的区别就是表达式中查询的时候没有走索引。
关于explain的详解可以参考MySQL Explain 使用详解,对返回结果的解释都很清楚。
2、多列索引
因为索引的种种好处,尤其是提高查询速度方面的优点,有人觉得在表的每个列上都加上索引是不是一个好的选择,想象很美好,但是现实是不行的。首先索引会占用一部分空间,而且索引也是需要维护,这样可能会导致数据变更时速度很慢。另外就是索引的顺序,因为索引遵循的最左优先的原则,而如果sql问题依然是不能使用的。因此,在多个列上建立独立索引在大多情况下并不能提高MySQL的查询性能。MySQL在5.0版本中引入了“index merge”的策略,一定程度上可以使用表上的多个单列索引来定位指定的行。
索引合并策略使用时机:
当服务器需要对多个索引做相交操作(即多个and条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引。
当服务器需要对多个索引做联合操作(多个or条件),这种情况通常需要消耗大量的CPU和内存资源在算法的缓存、排序记忆合并操作上。特别是当其中有些索引的选择性不高,需要合并扫描返回大量数据的时候。
还是通过一个实际操作来展示吧,现在给我的表上添加两个单列索引,然后通过查询看看结果是怎么样的。
看下图:
根据上图可以看出,单列索引在使用and查询时是没有生效的;而使用or查询时,用到了索引,type类型为"index_merge",使用到的索引为PRIMARY和age_index。
现在删除除主键索引之外的所有单列索引,并新建一个多列索引,同样使用and和or查询,如下图:
因为我在列age和mobile两列上加了一个索引,根据结果可以看出使用and查询和单列查询时使用了索引,而是用or查询没有使用索引,使用的是全表扫描。
3、顺序选择
创建多列索引时列的顺序是非常重要的,合理的索引顺序取决于你使用这个索引查询的内容,而且必须要如何更好的满足排序和聚集。在一个多列B-Tree索引中列顺序意味着这个索引按照最左列进行排序,其次是第二列,依此类推。并且,索引可以使用正向或者方向扫描,以满足order by、group by、distinct等与列顺序匹配的查询需求。
有一比较老的规则是将查询最多列放在最前面,这个好像没什么问题,我以前也觉得就该这样。在某些情况下这么做是没问题的,但是更多情况下它并不如避免随机I/O和排序重要。在不需要考虑排序或者聚集时把最常查询的列放在第一位可能是好的注意,在这种情况下索引的目的仅仅是为了优化where查询。下面看一个简单的例子:
select * from t_user where age = 22 and address = "us";
这种情况下我们的索引应该怎么创建,age_address_index(age,address)还是说应该反过来??查询一下满足每个条件的行总数,如下图:
根据上图可见满足age=4的数量是小于address="us"的数量的(说明情况就行),所以将age列放在第一列是合适的。
或者根据列的可选择性判断哪一列放在前面合适,如下图:
根据结果可以看出age列具有更高的可选择性,再次推断出将age列放在第一位。
上面只是比较简单的一种,实际上索引的优化还是有很多内容的,网上也有不少的资料。
4、聚簇索引
聚簇索引不是一种独立索引类型,而是一种数据存储方式,不同数据库存储引擎的实现细节也各有不同,而且,也不是所有的存储引擎都支持聚簇索引。MySQL的InnoDB引擎将索引和数据行存储在同一个数据结构中,当一个表有聚簇索引的时候,它数据行实际上存储在索引的叶子页,“聚簇”意思就是说具有相邻键值的行彼此靠近存储,也就是说如果键的值相邻,那么他们对应的数据也会相邻存储。每一个表只能有一个聚簇索引,因为你不可能一次把你的数据行存放到两个地方(覆盖索引是可以模拟多个聚簇索引的,有兴趣可以的可以网上看下聚簇索引)。
下面这个图片展示了一个聚簇索引的数据布局。叶子页包含了所有数据行,但是节点页只包含索引的列,这种情况下索引列包含整数值。
InnoDB引擎通过主键对数据进行“聚集”,也就是说上图中“索引列”就是主键列。如果没有定义主键,InnoDB会尝试使用唯一非空索引替换,如果没有这样索引,那么InnoDB会定义一个隐藏的主键,并将数据聚集到这个主键上去。 InnoDB仅仅在页面内将记录聚集在一起, 具有相邻键值的页可能彼此远离。一个聚集主键值确实提高性能,但是它也可能导致严重的问题,所以还是应该仔细考虑聚集这种情况,尤其是将表存储引擎从InnoDB改变成其他时候。
聚集索引有以下几个好处:
1、将相关聚集到一起,这个主要应该是减少了随机I/O。
2、数据访问速度更快,聚簇索引同时将索引和数据保存在一个B-Tree中,因此从一个聚集索引中获取数据要比从一个非聚集索引通过比较查询数据速度更快。
3、使用覆盖索引的查询时可以使用叶节点中包含的主键值。
同样,聚集索引也有缺点:
1、聚集为I / O绑定工作负载提供了最大的改进。但是如果数据保存在内存中,那么它的访问顺序并不重要,聚集索引不会带来太多好处。
2、插入数据的速度在很大程度上依赖插入的顺序,以主键顺序插入行是将数据加载到InnoDB表中的最快方法。如果没有按照主键顺序加载数据行,那么在加载大量数据之后使用OPTIMIZE_TABLE重新组织表是一个好主意。
3、更新聚簇索引的开销比较大,因为它强制将每一行需要更新数据都移动到新的位置。
4、当插入新的数据行行或者因为更新行的主键而导致必须移动行时,基于聚簇索引构建的表将进行页面拆分。当行的键值表面必须将行放入充满数据的页时就会发生页面拆分。存储引擎必须将页面拆分为两个
容纳行,页面拆分可能会导致表使用更多的磁盘空间。
5、聚簇索引可能会导致全表扫描速度变慢,尤其是因为页面拆分导致数据行不那么密集地打包或非顺序存储时。
6、辅助(非聚簇)索引可能比预期的要大,因为它们的叶节点包含引用行的主键列。
7、二级索引访问需要两个索引查找而不是一个。之所以需要经过两个索引查找是因为存储在辅助索引的“行指针”的特性,叶子节点存储的不是引用数据行物理位置的指针,而是数据行的主键值。也就意味着使用辅助索引查找数据行时,存储引擎先查找到辅助索引中存储的叶子节点,然后使用存储的主键值定位到主键然后查询到数据行。
5、覆盖索引
通常情况下都建议为where条件查询创建索引,这个当然是正确的,但是索引不应该仅仅为了where查询创建,而是应该为所有查询设计。索引确实是一种查询数据行的有效方式,MySQL也可以直接通过索引去获取某列的数据,而不需要读取所有的数据行。毕竟这个索引的叶子节点就包含了它们索引的值,所以当索引本身就包含了你想要的数据时候为什么还要去读取数据行呢?
所以什么是覆盖索引?包含(或者说“覆盖”)满足一个查询所需的所有数据的索引就称为覆盖索引。简单点说,就是这个索引本身包含了你想要的数据。覆盖索引是一个很强大的工具,可以极大提升数据库性能。考虑下只读取索引而不读取数据行的好处:
1、索引条目本身要比完整数据行小得多,因此MySQL可以访问更少的数据如果它只读取索引的话。这对于缓存工作负载非常重要,其中大部分响应时间来自复制数据。 并且它对I/O绑定工作负载也很有帮助,因为索引比数据更小、更适合内存。
2、索引根据它们的索引值排序(至少在页面内),因此I/O绑定的范围访问与访问随机硬盘位置上每一行相比,需要更少的的I/O。
3、某些存储引擎(如MyISAM)仅缓存MySQL内存中的索引。 由于操作系统为MyISAM引擎缓存数据,因此访问它通常需要系统调用。,这可能会对性能产生巨大影响,尤其是对于缓存的工作负载,即系统调用非常昂贵部分的数据访问。
4、覆盖索引对InnoDB引擎的数据库表是非常有帮助的,因为InnoDB的聚簇索引的。InnoDB的辅助索引在它们的叶子节点保存了行的主键值,这样一个覆盖查询辅助索引避免了其它索引在主键中查找。
覆盖索引并不是什么索引都可以,这个索引必须存储有它包含列的值。MySQL中只能使用B-Tree索引来覆盖查询,不同的存储引擎实现覆盖查询的方式也不相同,也不是所有的存储引擎都支持覆盖查询。
今天主要是简单了解了一下数据库索引的一些知识,因为最近又要出去面试了,感觉自己数据库方面挺薄弱的,优化什么的了解的都比较少,所以想临时突击一下。我看的是《高性能MySQL》这本书,感觉还是很不错的,只是缺少一些新的特性,毕竟MySQL已经更新到8.0版本了。另外这本书也挺厚的(700多页),有时候真的没有拿起来看得欲望(尴尬)。自己实际开发中也缺少大量数据开发场景,所以数据库优化方面经验比较少,不过实际归实际,理论知识也还是要具备的,毕竟理论指导实践嘛。最后一点我觉得还是重视数据结构和算法方面的知识,真的太太太重要了。好了,今天学习先到这里了,后面有时间继续学习数据库优化方面的知识。