Mysql——索引
数据结构
哈希表
可以O(1)的检索数据;
hash索引由于数据是随机的,不支持顺序和范围查询;
二叉查找树
性能依赖于平衡程度,最坏情况下会退化至O(n);
AVL树(自平衡二叉查找树)
进行 O(logn) 次数的旋转操作,需要频繁地进行旋转操作来保持平衡,会有较大的计算开销;
每个树节点仅存储一个数据,因此一次磁盘IO只能获取一个数据;
红黑树
插入和删除节点时只需进行 O(1) 次数的旋转和变色操作;
平衡性相对较弱,可能会导致树的高度较高,导致一些数据需要进行多次磁盘 IO 操作才能查询到;
每个树节点仅存储一个数据,因此一次磁盘IO只能获取一个数据;
更适合数据在内存的情况;
B树(多路平衡二叉树)
所有节点既存放key也存放data;
B+树
只有叶子节点存放 key 和 data,其他内节点只存放 key,因此检索的效率都是稳定的;
每个叶子节点有一条引用链指向与它相邻的叶子节点;进行范围查询时,只需要找到下界后,对链表进行遍历即可;
每个节点存放多个key或data,减少了树高,而且一次磁盘IO可以取多个值,减少了IO次数。
聚簇索引与非聚簇索引
在innodb中,主键索引是聚簇索引,即索引结构和数据一起存放的索引。
其他所有索引都是二级索引,也是非聚簇索引,这些索引存的是主键。当使用二级索引查询到主键后,还要再到主键索引去查询,称为回表。
不过非聚簇索引不一定回表查询,如果SQL查的字段正好被索引覆盖了,那就没必要回表了,这种称为覆盖索引。
联合索引
使用表中的多个字段创建索引,就是 联合索引,也叫 组合索引 或 复合索引。
最左前缀匹配原则
最左前缀匹配原则指的是在使用联合索引时,MySQL 会根据索引中的字段顺序,从左到右依次匹配查询条件中的字段。如果查询条件与索引中的最左侧字段相匹配,那么 MySQL 就会使用索引来过滤数据,这样可以提高查询效率。
最左匹配原则会一直向右匹配,直到遇到范围查询(如 >、<)为止。对于 >=、<=、BETWEEN 以及前缀匹配 LIKE 的范围查询,不会停止匹配。
原理:联合索引的底层如下图所示,可以看到,a 是全局有序的(1, 2, 2, 3, 4, 5, 6, 7 ,8),而 b 是全局是无序的(12,7,8,2,3,8,10,5,2)。只有在 a 相同的情况才,b 才是有序的,比如 a 等于 2 的时候,b 的值为(7,8),即局部有序。
因此如果查询 where a>2 and b=7
,可以使用到索引a,但在a>2的情况下,b是无序的,因此无法使用到索引b。
而如果查询 where a>=2 and b=7
,虽然a>2的时候用不了索引b,但在a=2的时候,b是有序的,因此可以使用到索引b,然后后面再用链表遍历。
索引下推
索引下推(Index Condition Pushdown,简称 ICP) 是 MySQL 5.6 版本中提供的一项索引优化功能,它允许存储引擎在索引遍历过程中,执行部分 WHERE
字句的判断条件,直接过滤掉不满足条件的记录,从而减少回表次数,提高查询效率。
对于查询SELECT * FROM user WHERE zipcode = '431200' AND MONTH(birthdate) = 3;
,其中的 birthdate 字段使用函数导致索引失效。
没有索引下推之前,即使 zipcode
字段利用索引可以帮助我们快速定位到 zipcode = '431200'
的用户,但我们仍然需要对每一个找到的用户进行回表操作,获取完整的用户数据,再去判断 MONTH(birthdate) = 3
。
有了索引下推之后,存储引擎会在使用zipcode
字段索引查找zipcode = '431200'
的用户时,同时判断MONTH(birthdate) = 3
。这样,只有同时满足条件的记录才会被返回,减少了回表次数。
MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处理查询解析、分析、优化、缓存以及与客户端的交互等操作,而存储引擎层负责数据的存储和读取,MySQL 支持 InnoDB、MyISAM、Memory 等多种存储引擎。
索引下推的下推其实就是指将部分上层(Server 层)负责的事情,交给了下层(存储引擎层)去处理。
除了可以减少回表次数之外,索引下推还可以减少存储引擎层和 Server 层的数据传输量。
索引失效
以下情况会导致索引失效:
- 当select * 且 where 范围查找过大时,有可能直接走全表扫描而不是使用索引;
- 联合索引,但未遵循最左匹配原则;
- 在索引列上进行计算、函数、类型转换等操作(因为索引保存的是索引字段的原始值,而不是经过函数计算后的值);
- 以 % 开头的 LIKE 查询比如
LIKE '%abc';
; - 查询条件中使用 OR,且 OR 的前后条件中有一个列没有索引,涉及的索引都不会被使用到;
- IN 或 NOT IN的取值范围较大时会导致索引失效,走全表扫描
- 发生隐式转换,当 where 查询操作符左边为字符类型时发生了隐式转换,会导致索引失效;
隐式转换
当操作符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容。某些转换是隐式发生的。
两个参数至少有一个是
NULL
时,比较的结果也是NULL
,特殊的情况是使用<=>
对两个NULL
做比较时会返回1
,这两种情况都不需要做类型转换;两个参数都是字符串,会按照字符串来比较,不做类型转换;
两个参数都是整数,按照整数来比较,不做类型转换;
十六进制的值和非数字做比较时,会被当做二进制串;
有一个参数是
TIMESTAMP
或DATETIME
,并且另外一个参数是常量,常量会被转换为timestamp;
有一个参数是
decimal
类型,如果另外一个参数是decimal
或者整数,会将整数转换为decimal
后进行比较,如果另外一个参数是浮点数,则会把decimal
转换为浮点数进行比较;所有其他情况下,两个参数都会被转换为浮点数再进行比较;
关于第7条,具体的:
不以数字开头的字符串都将转换为
0
。如'abc'
、'a123bc'
、'abc123'
都会转化为0
;以数字开头的字符串转换时会进行截取,从第一个字符截取到第一个非数字内容为止。比如
'123abc'
会转换为123
,'012abc'
会转换为012
也就是12
,'5.3a66b78c'
会转换为5.3
,其他同理。
因此,隐式转化可能导致索引失效,如下:
SELECT * FROM test1 WHERE num1 = '10000';
左边为 int 类型10000
,转换为浮点数还是10000
,右边字符串类型'10000'
,转换为浮点数也是10000
。两边的转换结果都是唯一确定的,所以不影响使用索引。SELECT * FROM test1 WHERE num2 = 10000;
左边是字符串类型'10000'
,转浮点数为 10000 是唯一的,右边int
类型10000
转换结果也是唯一的。但是,因为左边是检索条件,'10000'
转到10000
虽然是唯一,但是其他字符串也可以转换为10000
,比如'10000a'
,'010000'
,'10000'
等等都能转为浮点数10000
,这样的情况下,是不能用到索引的。