MySQL索引一(B+Tree)



一:索引的类型mysql

二:索引的优势sql

三:高性能索引策略bash

四:索引案例服务器



1.1类型介绍数据结构

索引有不少类型,能够为不一样场景提供更好的性能。在MySQL中,索引是在存储引擎层而不是服务器层实现的。因此,并无统一的索引标准:不一样存储引擎的索引的工做方式并不同,也不是全部的存储引擎都支持全部类型的索引,即便多个存储引擎支持同一种类型的索引,其底层的实现也可能不一样ide

 

 

1.2 B+Tree索引函数

存储引擎以不一样的方式使用B+Tree索引,性能也各有不一样,各有优劣。性能

例如MyISAM使用前缀压缩技术使得索引更小,但InnoDB则按照原数据格式进行存储。优化

再如MyISAM索引经过数据的物理位置引用被索引行,而InooDB则根据主键引用被索引的行spa



1.2.1 B+Tree索引数据结构

wKiom1WSEQyjIGKwAADjBorWus0298.jpg




1.2.2理解数据结构

假若有以下数据表:

CREATE TABLE people(
   last_name         varchar(50)         not null,
   first_name        varchar(50)         not null,
   dob                date                not null,
   gender            eum('m','f')        not null,
   key(last_name, first_name, dob)
)

对于每一行的数据,索引中包含了,last_name,first_name和dob列的值

wKiom1WSEWfBkvpuAAF1p7iERDw383.jpg

注意:索引对多个值进行排序的依据是CREATE TABLE语句中定义索引时列的顺序。看最后两个条目,两我的的姓和名都同样,则根据他们的出生日期来排列顺序




1.2.3 B+Tree索引对以下类型的查询有效

 

全值匹配:和索引中的全部列进行匹配,例如前面建立的索引可用于查找姓名为Allen Cuba,出生于1960-01-01的人

 

匹配最左前缀:前面建立的索引可用于查找全部姓为Allen的人,即只使用索引的第一列

 

匹配列前缀:前面建立的索引也能够只匹配某一列的值的开头部分,例如能够用于查找全部以J开头的姓的人

 

匹配范围值:前面建立的索引可用于查找姓在Allen和Barrymore之间的人。(这里只使用了索引的第一列

 

精确匹配某一列并范围匹配另一列:前面建立的索引可用于查找全部姓为Allen,而且名字是字母K开头(好比Kim)的人。即第一列last_name全匹配,第二列first_name范围匹配

 

只访问索引的查询:B+Tree一般能够支持“只访问索引的查询”,即查询只须要访问索引,而无须访问数据行。



由于索引树中的节点是有序的,因此除了按值查找以外,索引还能够用于查询中的ORDER BY操做。通常来讲,若是B+Tree能够按照某种方式查找到值,那么也能够按照这种方式用于排序。因此,若是ORDER BY 子句知足前面列出的几种查询类型,则这个索引也能够知足对应的排序需求




1.2.4 B+Tree索引的限制

 

1.2.4.1若是不是按照索引的最左列开始查找,则没法使用索引。例如上面例子中的索引没法用于查找名字为Kim的人,也没法查找某个特定生日的人,由于这两列都不是最左数据列。相似的,也没法查找姓氏以某个字母结尾的人

 

1.2.4.2不能跳过索引中的列。也就是说,前面所述的索引没法用于查找姓为Allen而且在某个特定日期出生的人。若是不指定名(first_name),则MySQL只能使用索引的第一列

 

1.2.4.3若是查询中有某个列的范围查询,则其右边全部列都没法使用索引优化查找。例若有查询WHERE last_name='Allen' ANDfirst_name LIKE 'K%' AND dob='1930-07-12',这个查询只能使用索引的前两列,由于这里LIKE是一个范围条件(可是服务器能够把其他列用于其它目的)。若是范围查询列值数量有限,那么能够经过使用多个等于条件来代替范围条件。尽量将范围查询放到最后,由于范围查询后的列不能使用索引




2.1索引的优势

索引可让服务器快速的定位到表的指定位置。可是这并非索引的惟一做用,到目前为止

 

能够看到,根据建立索引的数据结构不一样,索引也有一些其它的附加做用

最多见的B+Tree索引,按照顺序存储数据,因此MySQL能够用来作ORDER BY 和GROUPBY操做。由于数据是有序的,因此B+Tree也就会将相关的列值都存储在一块儿。最后,由于索引中存储了实际的列值,因此某些查询只使用索引就可以完成所有查询。据此特性,总结下来索引有以下三个优势:

1.索引大大减小了服务器须要扫描的数据量

2.索引能够帮助服务器避免排序和临时表

3.索引能够将随机I/O变为顺序I/O




Lahdenmaki和Leach提出,如何评价一个索引是否适合某个查询的“三星系统”:

A.索引将相关的记录放到一块儿则得到一星;

B.若是索引中的数据顺序和查找中的排序顺序一致则得到两星;

C.若是索引中的列包含了查询中须要的所有列则得到三星;




3.高性能的索引策略

正确的建立索引和使用索引是实现高性能查询的基础。如今讨论一下如何真正的发挥这些索引的优点

 

3.1独立的索引

一般看到一些查询不当的使用索引,或者使MySQL没法使用已有的索引。若是查询中的列不是独立的,则MySQL就不会使用索引。“独立的列”是指索引列不能是表达式的一部分,也不能是函数的参数

 

例1:下面这个查询就没法使用user_id列的索引

mysql> SELECT user_id FROM tb1 WHERE user_id+1 = 5;

很容易看出WHERE中的表达式起始等价于user_id=4,可是MySQL没法自动解析这个方程式,这彻底是用户行为。咱们应该养成简化WHERE条件的习惯,始终将索引列单独放在比较符号的一侧



例2:下面这个查询就没法使用date_col列的索引

mysql> SELECT ... WHERE TO_DAYS(CUURRENT_DATE) - TO_DAYS(date_col) <= 10

解决方法:在程序中获取当前时间,减去10天,加入最后的值存储在last_date_val中

mysql> SELECT ... WHERE date_col <= last_date_val



3.2前缀索引和索引选择性

有时候须要索引很长的字符列,这会让索引变得很慢。一般能够索引开始的部分字符,这样能够大大节约索引空间,从而提升索引效率。但这样也会下降索引的选择性。


索引选择性是指:不重复的索引值(也称为基数)和数据表的记录总数(#T)的比值,范围从1/#T到1之间。索引的选择性越高,则查询效率越高,由于选择性高的索引可让MySQL在查找时过滤掉更多的行。惟一索引的选择性是1,这是最好的索引选择性,性能也最好

 

理解索引选择:每行记录都有一个索引值,若是前缀过少,那么索引值重复的可能性就越大,这样在查询的时候查询到的数据就越多,过滤掉的数据就越少,因此要找一个合理的前缀。

 

合理前缀的诀窍在于:选择足够长的前缀以保证较高的选择性,同时又不能太长(以便节约空间)。前缀应该足够长,以使得前缀索引的选择性接近于索引整个列。换句话说,前缀的“基数”应该接近于完整列的“基数”



3.2.1如何选择合理前缀的长度

mysql> CREATE TABLE tb( 
    -> city VARCHAR(50) NOT NULL 
    -> ); 
Query OK, 0 rows affected (0.26 sec)


mysql> SELECT COUNT(*) AS cnt, city_name FROM city GROUP BY city_name  
       ORDER BY cnt DESC LIMIT 10;

wKiom1WSExGRd5CCAACrdQakpA4784.jpg


能够看到,上面每一个值都出现了5-7次,如今查找到最频繁出现的城市,先从3个前缀字母开始



mysql> SELECT COUNT(*) AS cnt, LEFT(city_name,3) AS pref FROM city  
     GROUP BY pref ORDER BY cnt DESC LIMIT 10;

wKiom1WSE0exPCeEAACEcYYoOCk265.jpg

每一个前缀都比原来的城市出现的次数更多,所以惟一前缀比惟一城市要少的多。而后增长前缀的长度,知道这个前缀的选择性接近完整列的选择性。


mysql> SELECT COUNT(*) AS cnt, LEFT(city_name,7) AS pref FROM city 
      GROUP BY pref ORDER BY cnt DESC LIMIT 10;

wKioL1WSFW7x24VIAACGzV-o-A8032.jpg



计算合适前缀的长度的方法就是计算完整列的选择性,并使前缀的选择性接近于完整列的选择性。下面是如何计算完整列的选择性:


mysql> SELECT COUNT(DISTINCT city_name)/COUNT(*) FROM city;

wKiom1WSE9aR2ytSAACnZqRYNFs678.jpg

一般来讲(尽管也有例外状况),这个例子中若是前缀的选择性可以接近于0.4317,基本上就可用了,能够在一个查询中针对不一样前缀长度计算。

wKioL1WSFcHBWKvjAAHhOPOz4Ao061.jpg

显示长度为7的时候,再增长前缀长度,选择性已经没有提高了(或者某些时候提高幅度很小了)




3.2.2建立前缀索引

mysql> ALTER TABLE city ADD [KEY|INDEX] city_name_index(city_name(7));

前缀索引是一种能使索引更小、更快的有效方法,但另外一方面也有其缺点:MySQL没法使用前缀索引作ORDER BY 和 GROUP BY ,也就没法使用前缀索引作覆盖扫描




3.3多列索引

不少人对多列索引的理解还不够。一个常见的错误就是,为每一个列建立独立的索引,或者按照错误的顺序建立多列索引

CREATE TABLE t(
    c1 INT,
    c2 INT,
    c3 INT,
    KEY(c1),
    KEY(c2),
    KEY(c3)
);

这种索引策略,通常是因为人们听到一些说,“把WHERE条件里面的列都建上索引”致使的。其实是很是错误的,这样一来最好的状况下也只能是“一星”索引,其性能比起真正最有的索引可能差几个数量级。有时候若是没法设计一个“三星”索引,那么不如忽略掉WHERE子句,集中精力优化索引列的顺序,或者建立一个全覆盖索引

 

 

在多个列上创建独立的单列索引大部分状况下并不能提升MySQL的查询性能。MySQL5.0和更新版本引入了一种叫“索引合并”的策略,必定程度上可使用表上的多个单列索引来定位指定的行。

 

更早版本的MySQL只能使用其中某一个单列索引,而后这种状况下没有哪个独立的单列索引是很是有效的。例如:表film_actor在字段film_id和actor_id上各有一个单列索引,但对于下面这个查询WHERE条件,这两个单列索引都不是好的选择


mysql> SELECT film_id,actor_id FROM film_actor WHERE actor_id=1OR film_id=1;

在老的MySQL版本中,MySQL对这个查询会使用全表扫描。除非改写成以下的两个查询。

mysql> SELECT film_id,actor_id FROM film_actor WHERE actor_id=1  
-> UNION ALL 
-> SELECT film_id,actor_id FROM film_actor WHERE film_id=1 AND actor_id<>1;




3.4选择合适的索引列顺序

3.4.1例子分析

mysql> SELECT * FROM payment WHERE staff_id = 2 AND customer_id = 584;

是建立一个(staff_id, customer_id)索引仍是应该颠倒顺序呢?能够作一些查询来肯定这个表中值的分布状况,并肯定哪一个列的选择性更高。

mysql> SELECT SUM(staff_id=2),SUM(customer_id=584) FROM payment;

wKiom1WSLdzSk0RpAADdFqYfkxg181.jpg

mysql> SELECT COUNT(DISTINCT(staff_id))/COUNT(*) AS staff_id ,
              COUNT(DISTINCT(customer_id))/COUNT(*) AS customer_id ,
              COUNT(*) 
      FROM payment;

wKiom1WSLhiy_4SQAADxTNmsqYQ085.jpg







wKiom1WSLwKx5lvLABL1dzOLnB4167.jpg

相关文章
相关标签/搜索