索引的优点:
索引大大减少了服务器需要扫描的数据量
索引可以帮助服务器避免排序和临时表
索引可以将随机I/O变为顺序I/O
要理解MySQL中索引是如何工作的,最简单的方法就是看一本书的“索引”部分,如果想在一本书中找到某个特定主题,一般先看书的”索引“,找到对应的页码
在MySQL中,存储引擎用类似的方法使用索引,其现在索引中找到对应值,然后根据匹配的索引记录找到对应的数据行
索引可以包含一个或者多个列的值。如果包含多个列,那么列的排列顺序也十分重要,MySQL只能高效地使用索引的最左前缀列
在MySQL中,索引是在存储引擎层实现的,不同存储引擎的索引的工作方式也不相同,也不是所有的存储引擎都支持所有类型的索引。即使多个存储引擎都支持同一种类型的索引,其底层的实现也可能不相同。
MySQL支持的索引类型,以及优缺点
InnoDB使用的是B+Tree
B-Tree通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同。
B-Tree索引能够加快访问数据的速度,因为存储引擎不再需要进行全表扫描
来获取需要的数据,取而代之的是从索引的根节点
开始进行搜索。
根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找。通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点,这些指针实际上定义了子节点页中值的上限和下限。最终存储引擎要么是找到对应的值,要么该记录不存在。
叶子节点比较特别,它们的指针指向的是被索引的数据。
B-Tree对索引列是顺序组织存储的,所以很适合查找范围数据
可以使用B-Tree索引的查询类型
全值匹配:全值匹配指的是和索引中的所有列进行匹配,查找last_name、first_name以及dob全都匹配的人
匹配最左前缀:查找所有last_name相同的人,只使用索引的第一列
匹配列前缀:类似查找所有以J开头的last_name的人,只匹配某一列的值的开头部分,这里也只使用了索引的第一列
匹配范围值:查找last_name在Allen和Barrymore之间的人。这里也使用索引的第一列
精确匹配某一列并范围匹配另外一列:即last_name全匹配,第二列first_name范围匹配
索引树中的节点是有序的,所以除了按值查找之外,还可以用于查询中的ORDER BY
操作(按顺序查找)
B-Tree索引的限制
如果不是按照索引的最左列开始查询,则无法使用索引
不能跳过索引的列
如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找
哈希索引(hash index)基于哈希表实现的,只有精确匹配索引所有列的查询才有效
对于每一行数据,存储引擎都会对所有的索引列
计算计算一个哈希码(hash code)
哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。
哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针
注意:在MySQL中只有Memory引擎显示支持哈希索引,Memory引擎同时也支持B-Tree索引。
如果多个列的哈希值相同,索引会以链表的方式存放多个记录指针到同一个哈希条目中
注意:每个槽Slot的编号是顺序的,但是数据行不是
MySQL先计算peter的哈希值,并使用该值找到对应的记录指针。因为f('peter')=8784
,所以MySQL在所有中查找8784,可以找到指向第3行的指针,最后一步就是比较第三行的值是否为'peter',以确保就是要查找的行
索引自身只存储对应的哈希值
哈希索引的一些限制:
哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行
哈希索引并不是按照索引值顺序存储的,所以不能用于排序
哈希索引只支持索引列的全部内容来计算哈希值
哈希索引只支持等值比较查询,包括=、IN()、<=>
访问哈希索引的数据非常快,除非有很多哈希冲突(也就是不同的索引列值却又相同的哈希值)。当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行比较,直到找到所有符合条件的行
哈希冲突很多的话,索引维护操作的代价会很高
InnoDB引擎有一个特殊的功能叫做“自适应哈希索引(adaptive hash index)”。当InnoDB注意到某些索引值被使用得特别频繁地时候,他会在内存中基于B-Tree索引之上再创建一个哈希索引,这样B-Tree也具有哈希索引的一些有点,比如快速的哈希查找。这是一个完全自动的、内部的行为,用户无法控制或者配置,不过如果有必要的话,完全可以关闭该功能
MyISAM 表支持空间索引,可以用作地理数据存储。和B-Tree索引不同,这类索引无须前缀查询。空间索引会从所有维度来索引数据。查询时,可以有效地使用任意维度来组合查询。必须使用 MySQL的 GIS 相关函数如MBRCONTAINS()等来维护数据。MySQL的 GIS 支持并不完善,所以大部分人都不会使用这个特性。开源关系数据库系统中对GIS的解决方案做得比较好的是 PostgreSQL的PostGIS
全文索引是一种特殊类型的索引,它查找的是文本中的关键词,而不是直接比较索引中的值。全文搜索和其他几类索引的匹配方式完全不一样。它有许多需要注意的细节,如停用词、词干和复数、布尔搜索等。全文索引更类似于搜索引擎做的事情,而不是简单的WHERE条件匹配。
在相同的列上同时创建全文索引和基于值的 B-Tree 索引不会有冲突,全文索引适用于MATCH AGAINST操作,而不是普通的WHERE条件操作
在MySQL中,索引有两种分类方式:逻辑分类和物理分类
按照逻辑分类,索引可分为:
主键索引:一张表只能有一个主键索引,不允许重复、不允许为 NULL;
唯一索引:数据列不允许重复,允许为 NULL 值,一张表可有多个唯一索引,但是一个唯一索引只能包含一列,比如身份证号码、卡号等都可以作为唯一索引;
普通索引:一张表可以创建多个普通索引,一个普通索引可以包含多个字段,允许数据重复,允许 NULL 值插入;
全文索引:让搜索关键词更高效的一种索引。
按照物理分类,索引可分为:
聚簇索引(聚集索引):通俗来说,找到了索引就找到了需要的数据,那么这个索引就是聚簇索引,所以主键就是聚簇索引,修改聚簇索引其实就是修改主键
非聚簇索引:索引的存储的和数据的存储是分离的,也就是说找到了索引但是没找到数据,需要根据索引上的值(主键)再次回表查找,非聚簇索引也叫做辅助索引
独立的列是指索引列不能是表达式的一部分,也不能是函数的参数
select actor_id from sakila.actor where actor_id + 1 = 5;
上述例子就无法使用索引,所以我们需要养成简化where条件的习惯,始终将索引列单独放在比较符号的一侧
需要索引很长的字符列的时候通常会让所有变得大且慢,一种策略是模拟哈希索引,另一种可以通过索引开始部分的字符,节约索引空间,从而提高索引效率
索引的选择行是指,不重复的索引值(基数 cardinality)和数据表的记录总数(#T)的比值count(distinct(列))/count(*)
索引的选择性越高则查询的效率越高,可以利用索引的选择性去选择前缀索引的长度
查询显示当前长度为7的时候,再增加前缀长度,选择性提升幅度已经比较小了
在选择索引的时候,不应该只看平均选择性,如果数据分布存在不均匀的现象很有可能是个陷阱
如何创建前缀索引
ALTER TABLE sakila.city_demo ADD KEY(city(7))
前缀索引是一种能使索引更小、更快的有效方法,但另一方面也有其缺点:MySQL无法使用前缀索引做ORDER BY
和 GROUP BY
,也无法使用前缀索引做覆盖扫描
并不是说将“WHERE条件里面的列都建上索引”就是好的,在多个列上建立独立的索引大部分情况下并不能提高MySQL的查询性能。MySQL5.0和更新版本引入了一种叫“索引合并(index merge)”的策略,一定程度上可以使用表上的多个单列索引来定位指定的行
索引合并,查询能够同时使用多个单列索引进行扫描,并将结果进行合并。这种算法有三种变种:
OR条件的联合(union)
AND条件的相交(intersection)
组合前两种情况的联合及相交
索引合并策略有时候是一种优化的结果,但实际上更多时候说明了表上的索引建得很糟糕
当出现服务器对多个索引做相交操作时(通常有多个AND条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引
当服务器需要对多个索引做联合操作时(通常有多个OR条件),通常需要耗费大量的CPU和内存资源在算法的缓存、排序和合并操作上。
更重要的是,优化器不会把这些计算到“查询成本”(cost)中,优化器只关心随机页面读取。这会使得查询的成本被“低估”,导致该执行计划还不如直接走全表扫描。通常来说,还不如将查询改为UNION的方式往往更好
如果再EXPLAIN中如果看到有索引合并,最好检查一下查询和表的结构,看是不是已经是最优了
在一个多列的B-Tree索引中,索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列等等。所以,索引可以按照升序或者降序进行扫描,以满足精确符合列顺序的ORDER BY、GROUP BY和DISTINCT等子句的查询需求
对于如何选择索引的列顺序有个经验法则:将选择性最高的列放在索引的最前列