一、Mysql索引基于B+树
B+树基于平衡二叉查找树和B+树。所谓平衡二叉查找树,就是任意节点的2个子树的最大高度差是1。平衡二叉树比非平衡二叉树的查找效率要高的多,平均时间复杂度是O(log2n)。为了保持二叉树的平衡性,插入和删除节点时往往要进行左旋和右旋操作。
B+树是为磁盘和其他直接存取辅助设备设计的一种平衡查找树。在B+树中,所有记录节点都是按键值大小顺序存放在同一层的叶子节点上,由各叶子节点指针进行连接。B+树的查找时间复杂度是O(logmn),其中m是节点的子树个数。
B+树的特点:1. 是一棵n叉树;2. 节点有序;3. 非叶子节点不存储数据只存储索引,同样大小的磁盘页可以容纳更多的节点元素。所以数据量相同的情况下,B+树比B树更加“矮胖“,因此使用的IO查询次数更少。
B+树的插入操作是通过页的拆分和旋转来维持B+树的平衡,删除操作由于受填充因子的影响需要页合并来维持平衡。
在数据库中B+树的高度一般在2-4层,也就是说查找某一键值的行记录时最多需要2到4次IO。
二、索引分为聚集索引和辅助索引
聚集索引:叶子节点存放整行数据,通常主键索引即为聚集索引。在Innodb存储引擎中,存储引擎表就是索引组织表,表中数据就是按照主键顺序存放的。页内的数据以及页之间都是以双向链表连接。通过聚集索引树进行搜索,可以直接查找到整行数据。
辅助索引:叶子节点存储的是键值以及主键。在Innodb存储引擎中,通过辅助索引树进行搜索找到的是聚集索引键,因此如果要找到整行数据还需要通过聚集索引树搜索,这个过程称为回表。
三、查看索引
查看索引的命令:show index from table。一个只有主键索引的表执行改命令得到的结果可能如下:
结果中字段解释如下:
1. Table:表名称;
2. Non_unique:该索引是否包含重复值。主键索引的列值是唯一的。
3. Key_name:索引名称。PRIMARY代表主键索引。
4. Seq_in_index: 索引中序列的序列号,从1开始,如果是组合索引 那么按照字段在建立索引时的顺序排列 如 ('c1', 'c2', 'c3') 那么 分别为 1, 2, 3。
5. Column_name:列的名称。
6. Collation: 列以什么方式存储在索引中。在MySQL中,有值‘A’(升序)或NULL(无分序)。
7. Cardinality:索引中唯一值的数目的估计值,优化器会根据这个值来判断是否使用这个索引。对于大表而言计算这个值可能需要很长时间,因此这个值并不是实时更新的。通过运行ANALYZE TABLE 或者 myisamchk -a 来更新。
8. Sub_part:索引的长度,如果是部分被编入索引 则该值表示索引的长度 ,如果是整列被编入索引则为null。
9. Packed:指示关键字如何被压缩。如果没有被压缩,则为NULL。
10. Null:如果该列的值有NULL,则是YES,否则为NO。
11. Index_type:索引类型(BTREE, FULLTEXT, HASH, RTREE)
12. Commnet:关于在其列中没有描述的索引的信息。
13. Index_comment:为索引创建时提供了一个注释属性的索引的任何评论。
四、管理索引
1. 创建索引
创建索引可以使用 create index 或者 alter table xxx add index 语句来实现。
用户可以设置对整个列进行索引,也可以只索引一个列的开头部分数据。
一般来说,在WHERE和JOIN中出现的列需要建立索引,但也不完全如此,因为MySQL只对<,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE才会使用索引。
2. 删除索引
删除索引可以使用 drop index 或者 alter table xxx drop index 语句来实现。
五、cardinality
一般而言,高选择性的列创建索引比较合适。所谓高选择性,即不同值的各数和记录总数越接近越好。可以通过 show index 的 cardinality 来观察。
1. cardinality 何时更新:表中 1/16的数据发生过变化 或者 表中数据发生活 20亿次变化;执行语句 analyze table、show table status、show index 时会触发更新。
2. cardinality 如何计算:该值是基于采样统计出来的(在叶子节点中随机取8个节点,按照 n *N /8 计算而来。n为这8个节点中不同值的个数),且每次结果可能不同。
六、联合索引
假定2个键值的名称分别是a、b,创建索引(a,b),其B+树如下图所示。
联合索引的键值也是按照逻辑顺序排序的。
对于查询 SELECT * FROM TABLE WHERE a=xxx and b=xxx,可以使用到(a,b)这个联合索引,对于单个a列的查询 SELECT * FROM TABLE WHERE a=xxx也可以使用(a,b)这个联合索引,但是对于b列的查询则无法使用到这个联合索引。因为b列的值分别为1、2、1、4、1、2,显然不是排过序的。
当有单个索引和联合索引都可以使用时,优化器可能会选择单个索引,因为单个索引理论上一页存放的节点更多,IO次数更少。
如果联合索引能够覆盖排序字段的话,优化器可能会选择联合索引,因为这样无须再做额外的排序。仍然以上图为例,假设有列a上的索引index_a以及列(a,b)上的联合索引index_a_b,对于查询语句 SELECT * FROM TABLE WHERE a=xxx order by b,优化器将会选择index_a_b。
七、索引覆盖
所谓索引覆盖指的是不需要通过回表,只需要查询非聚集索引就能够查到需要的所有字段。在SQL执行计划里,如果Extra信息包括 Using Index 则表明使用了覆盖索引。
优化器会根据where条件和select_list里面的字段决定在使用一个索引(sta)后,是否需要回表—回到聚集索引取数据。基本的做法是:在确定了一个索引后,将select_list和where中出现的所有字段都拿来判断一下,如果字段都存在于sta索引中,则可以使用覆盖索引。如现有索引(a,b),语句 select count (*) from table where b > 10 and b < 20 是可以用到覆盖索引的,因为联合索引 (a, b) 覆盖了查询需要的字段b,换句话说,通过索引(a,b)的索引树查找到叶子节点并按照条件进行过滤即可得到结果,无需回表。
当有多个索引能够覆盖扫描时,优化器会选择效率最高的 ( IO次数最少的)。例如对于 SELECT COUNT(*) FROM TABLE 这样的查询,如有主键索引和非聚簇索引,优化器会选择非聚簇索引,因为非聚簇索引节点数据更小,io次数更少。
八、不使用索引的情况
对于不能进行索引覆盖的情况,优化器选择辅助索引的情况是:通过辅助索引查找到的数据是少量的。这是由当前硬盘的特性所决定的,即利用顺序读来替换随机读的查找。若用户使用的磁盘是固态硬盘,随机读操作非常快,同时有足够的自信来确认使用辅助索引可以带来更好的性能,那么可以使用关键字 FORCE INDEX 来强制使用某个索引。
假设表employees上有索引 index_hire_date(hire_date),语句 select * from employees where hire_date > '1990-01-01' 并不会使用辅助索引而是全表扫描,正是因为查询条件查找到的数据量可能很大,对这些数据随机读取数据行会很耗时。而 语句 select * from employees where hire_date = '1990-01-01' 则会使用辅助索引。
九、MRR优化
Multi-Range Read优化的目的是减少磁盘随机访问,将随机访问转换为顺序访问。其工作方式为:1. 将通过辅助索引查到的辅助索引键和主键对存储在缓存中;2. 缓存满了后根据主键进行排序;3. 根据排序后的主键来访问表中数据。
给出一个简单的例子,在innodb表执行下面的查询:
SELECT non_key_column FROM tbl WHERE key_column=x
在没有MRR的情况下,它是这样得到结果的:
1. select key_column, pk_column from tb where key_column=x order by key_column ---> 假设这个结果集是t
2. for each row in t ; select non_key_column from tb where pk_column = pk_column_value。(在oracle里第2步叫回表)
在有MRR的情况下,它是这样执行的:
1. select key_column, pk_column from tb where key_column = x order by key_column ---> 假设这个结果集是t
2. 将结果集t放在buffer里面(直到buffer满了),然后对结果集t按照pk_column排序 ---> 假设排序好的结果集是t_sort
3. select non_key_column fromtb where pk_column in (select pk_column from t_sort)
两者的区别主要是两点:
1. 没有MRR的情况下,随机IO增加,因为从二级索引里面得到的索引元组是有序,但是他们在主键索引里面却是无序的,所以每次去主键索引里面得到non_key_column的时候都是随机IO。(如果索引覆盖,那也就没必要利用MRR的特性了,直接从索引里面得到所有数据)
2. 没有MRR的情况下,访问主键索引的次数增加。没有MRR的情况下,二级索引里面得到多少行,那么就要去访问多少次主键索引(也不能完全这样说,因为mysql实现了BNL),而有了MRR的时候,次数就大约减少为之前次数t/buffer_size。
此外,MRR还会将某些范围查询拆分成键值对,以此来进行批量的数据查询。例如:SELECT * FROM t WHERE key_column1 > 1000 AND key_column1 < 2000 and key_column2 = 10000,表中有(key_column1, key_column2)的联合索引。如果没有MRR,优化器会在联合索引树中取出 key_column1 在1000到2000之间的数据,再过滤出来 key_column2 =10000的数据。这会导致大量无用数据被取出。启用MRR后,优化器将查询条件拆分为 (1000, 100000), (1001, 10000)……(1999, 10000),直接查询出数据。
十、Index Condition Pushdown 优化
在开启ICP之前,优化器根据索引取得主键后,再根据主键取得相应记录后再进行筛选。
开启ICP后,优化器在取出索引的同时会判断是否可以进行where条件的过滤,如果可以过滤则直接进行过滤,然后再取得相应的记录。
假设某张表有联合索引(zip_code, last_name, first_name),对于查询语句 SELECT * FROM TABLE WHERE zip_code = '90988' And last_name LIKE '%etrunia%' AND address LIKE '%Main Street%'。由于2个like条件是非固定前缀匹配,因此只能通过该索引匹配出zip_code=90988的索引节点。如果不开启ICP,则数据库需要先通过索引取出所有zip_code = 90988 的记录,然后再过滤where条件。开后ICP后,取出 zip_code = 90988的同时会在索引中过滤出来 last_name 和 address 符合条件的索引,再取出相关记录。
使用了ICP的执行计划Extra列会显示 Using Index Condition。
十一、哈希索引
Innodb引擎本身不支持手动创建哈希索引,Innodb存储引擎会监控对表上二级索引的查找,如果发现某二级索引被频繁访问,二级索引成为热数据,会自动建立哈希索引来提升速度。
哈希表也为散列表,又直接寻址改进而来。在哈希的方式下,一个元素k处于h(k)中,即利用哈希函数h,根据关键字k计算出槽的位置。函数h将关键字域映射到哈希表T[0...m-1]的槽位上。有可能将两个不同的关键字映射到相同的位置,这叫做碰撞,在数据库中一般采用链接法来解决。在链接法中,将散列到同一槽位的元素放在一个链表中。
Hash索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以Hash索引的查询效率要远高于B-Tree索引。
哈希索引的限制:
1. 哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行(即不能使用哈希索引来做覆盖索引扫描),不过,访问内存中的行的速度很快(因为memory引擎的数据都保存在内存里),所以大部分情况下这一点对性能的影响并不明显。
2. 哈希索引数据并不是按照索引列的值顺序存储的,所以也就无法用于排序
3. 哈希索引也不支持部分索引列匹配查找,因为哈希索引始终是使用索引的全部列值内容来计算哈希值的。如:数据列(a,b)上建立哈希索引,如果只查询数据列a,则无法使用该索引。
4. 哈希索引只支持等值比较查询,如:=,in(),<=>(注意,<>和<=>是不同的操作),不支持任何范围查询(必须给定具体的where条件值来计算hash值,所以不支持范围查询)。
5. 访问哈希索引的数据非常快,除非有很多哈希冲突,当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找到所有符合条件的行。
6. 如果哈希冲突很多的话,一些索引维护操作的代价也很高,如:如果在某个选择性很低的列上建立哈希索引(即很多重复值的列),那么当从表中删除一行时,存储引擎需要遍历对应哈希值的链表中的每一行,找到并删除对应的引用,冲突越多,代价越大。
十二、 Mysql 执行计划
各字段详解
id
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序 。
select_type
查询的类型,主要是用于区分普通查询、联合查询、子查询等复杂的查询
1、SIMPLE:简单的select查询,查询中不包含子查询或者union
2、PRIMARY:查询中包含任何复杂的子部分,最外层查询则被标记为primary
3、SUBQUERY:在select 或 where列表中包含了子查询
4、DERIVED:在from列表中包含的子查询被标记为derived(衍生),mysql或递归执行这些子查询,把结果放在零时表里
5、UNION:若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为derived
6、UNION RESULT:从union表获取结果的select
type
访问类型,sql查询优化中一个很重要的指标,结果值从好到坏依次是:
system>const>eq_ref>ref> fulltext > ref_or_null > index_merge > unique_subquery > index_subquery >range>index>ALL
一般来说,好的sql查询至少达到range级别,最好能达到ref
1、system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,可以忽略不计
2、const:表示通过索引一次就找到了,const用于比较primary key 或者 unique索引。因为只需匹配一行数据,所有很快。如果将主键置于where列表中,mysql就能将该查询转换为一个const
3、eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键 或 唯一索引扫描。
4、ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质是也是一种索引访问,它返回所有匹配某个单独值的行,然而他可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体。
5、range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了那个索引。一般就是在where语句中出现了bettween、<、>、in等的查询。这种索引列上的范围扫描比全索引扫描要好。只需要开始于某个点,结束于另一个点,不用扫描全部索引。
6、index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常为ALL块,应为索引文件通常比数据文件小。(Index与ALL虽然都是读全表,但index是从索引中读取,而ALL是从硬盘读取)。
7、ALL:Full Table Scan,遍历全表以找到匹配的行。
possible_keys
查询涉及到的字段上存在索引,则该索引将被列出,但不一定被查询实际使用。
key
实际使用的索引,如果为NULL,则没有使用索引。
查询中如果使用了覆盖索引,则该索引仅出现在key列表中。
key_len
表示索引中使用的字节数,查询中使用的索引的长度(最大可能长度),并非实际使用长度,理论上长度越短越好。key_len是根据表定义计算而得的,不是通过表内检索出的。
ref
显示索引的那一列被使用了,如果可能,是一个常量const。
rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
Extra
不适合在其他字段中显示,但是十分重要的额外信息
1、Using filesort:
mysql对数据使用一个外部的索引排序,而不是按照表内的索引进行排序读取。也就是说mysql无法利用索引完成的排序操作成为“文件排序”。
2、Using temporary:
使用临时表保存中间结果,也就是说mysql在对查询结果排序时使用了临时表,常见于order by 和 group by。
3、Using index:
表示相应的select操作中使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高
如果同时出现Using where,表明索引被用来执行索引键值的查找(参考上图)
如果没用同时出现Using where,表明索引用来读取数据而非执行查找动作。
覆盖索引(Covering Index):也叫索引覆盖。就是select列表中的字段,只用从索引中就能获取,不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。
注意:
a、如需使用覆盖索引,select列表中的字段只取出需要的列,不要使用select *
b、如果将所有字段都建索引会导致索引文件过大,反而降低crud性能
4、Using where :
使用了where过滤
5、Using join buffer :
使用了链接缓存
6、Impossible WHERE:
where子句的值总是false,不能用来获取任何元祖。
7、select tables optimized away:
在没有group by子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段在进行计算,查询执行计划生成的阶段即可完成优化
8、distinct:
优化distinct操作,在找到第一个匹配的元祖后即停止找同样值得动作