索引前言
索引,简单来说,就是数据库的目录。目的是为了提高查询效率。
索引依赖于数据结构,所以每一种索引除了自身的优缺点外,还有该数据结构的优缺点:
数据结构 | 适用场景 | 使用场景时间复杂度 | 缺点 |
---|---|---|---|
hash索引 | 等值查询 | O(1) | 不适合范围查询 |
有序数组 | 静态存储引擎 | 二分查找O(log(N)) | 插入慢 |
N叉树 | 快速查找和写入 | O(log(N)) | N偏小磁盘IO频繁 |
ps:
N越小,树越高。每个叶子节点存在一个数据块中,每个块通过链式链接。树高多少,就要一路照下来多少个块,从根到叶。所以树高一般为磁盘IO访问次数。N可以通过page大小来间接控制(5.7(+))。
树的树根的数据块总是在内存中,其第二层节点也有很大概率在内存中。其叶节点在磁盘中。
InnoDB索引模型
InnoDB索引采用B+Tree数据结构,即B+Tree索引模型。
//TODO 缺少一张B+Tree的图。
主键索引(也叫聚簇索引)的叶子节点存储的是整行数据
。
非主键索引(也叫二级索引)的叶子节点存储的是主键的值
。
回表:根据非主键索引搜索完毕,又根据主键索引进行查询。如select * from T where name='xxx',假设在name上索引,在根据name查询在name的非主键索引上进行查询得到对应行的ID后,然后根据这些ID在主键索引上进行查询。
所以,在查询时,尽量以主键为条件进行查询。
索引维护
页分裂
页合并
主键索引
在建表时,一般都会让带有自增主键。根据上面页分裂和页合并的过程,因为自增主键都是在后面追加,不会对前面的数据有变动,不会触发叶子节点的分裂。而有业务逻辑的字段做主键是无序的,会触发叶子节点的分裂。
主键索引还要考虑每个索引里的值长度大小。比如存1,2,3...等自增数字比身份证号码就占用的空间小的很多。使用整形(int,4个字节)就比长整型(bigint,8个字节)就小得多。
同时,主键也会存储到其他非主键索引中。
综上所述,主键索引的建立有以下要求:
1. 主键优先采用自增主键;
2. 主键类型越小越好。
业务字段适合做主键的场景(K-V场景):
- 只有一个索引;
- 该索引必须是唯一索引。
联合索引
覆盖索引
设有联合索引index_a(name,age),在根据name获取所有信息时,先去该索引根据name来获取主键id,然后根据主键id来获取该数据。这种通过非主键索引查询后再通过主键索引查询信息的过程,叫做回表
。在根据name获取age时,则会直接在该索引上通过name获取age并返回,不会去走主键索引,针对于这种情况的索引叫做覆盖索引
。
最左前缀原则
设有联合索引index_a(name,age),在针对于name的精确查询
或者name like '张%'
的情况,都还是会走该索引的。
因为name在最左边,所以单独使用name的精确查询是生效的。基于这一点,name like '张%'这种左边确定的查询也是生效的。
在建立联合索引时,索引内的字段的顺序是很重要的。
ps:如果是联合索引index_b(a,b,c),从左向右,有哪个字段哪个字段生效,如果中间的字段没有,那么从该字段向后的所有字段都不生效,只生效前面的字段。比如where a=1 and c=1,此时只有a生效。
索引下推
该优化是MySQL5.6加入的。
设有联合索引index_a(name,age),在where name like '王%' and age>20时,会在使用索引index_a的时候,进行判断age>20(以前是只做name like '王%'),减少回表次数。
索引与排序
设有联合索引index_a(name,age),在where name like '王%' order by age会走索引。
在where条件上的数据量很多时,即便有索引页不会使用的。因为MySQL认为此时全表扫描更快。
普通索引和唯一索引
在了解其区别前,需要了解到数据库成本最高的开销之一就有将数据读到磁盘中。
读数据
普通索引和唯一索引的在读数据时的差距是很小的:
- 普通索引在取数据时,找到这一条数据,然后指针判断下一条是否符合,不符合即返回结果;
- 唯一索引在取数据时,取到一条数据时,立即返回。
因为InnoDB是按照页来存取的,偶尔会发生下一条在另外一个页上的情况。一页是16K,对于整形字段,一页可以放近千个值。
写数据
普通索引和唯一索引在写数据时的差距是很大的:
这时分两种情况:要修改的数据页是否在内存中。
在内存中:
- 对于普通索引,找到数据,修改内存页中的数据,END;
- 对于唯一索引,找到数据,判断没有冲突后修改数据,END;
不在内存中:
- 对于普通索引,将修改记录保存到change Buffer中,END;
- 对于唯一索引,从磁盘读取数据,找到数据,判断没有冲突后修改数据,END;
change buffer
该功能只针对于 普通索引 有效。
含义
在不影响数据库一致性的前提下,如果数据页不在内存中,将数据写入到change buffer中,在下次读取这条数据时,从内存页中读取该数据,并执行change buffer与这个页相关的操作,从而保证数据逻辑的正确性。
change buffer是可以持久化的,它在内存和磁盘
上都有。
更新契机
除了访问会merge change buffer,后台会有定时线程merge change buffer。在数据库关闭时也会进行merge。
大小设置
change buffer占用的是buffer pool的内存,buffer pool是数据库关键的内存,常用的操作有回表等。通过innodb_change_buffer_max_size
参数设置,为50时,表示占用50%的buffer pool空间。
使用场景
写多读少。
如果是读多写少,或者读写相当,那么就需要考虑维护change buffer的开销了。
与redo log的关系
从写的角度:
从读的角度:
读的时候不需要在走redo log。
索引的使用
前缀索引的使用
一般针对于邮箱等,可能需要建立前缀索引。而索引的长度需要分析数据库得知。
通过下面的语句分析出前缀索引的长度:
SELECT 100*count(DISTINCT(left(email,7)))/count(*) from t3;
前缀索引需要一定的损失率来节省空间。一般为5%,即上面的结果大于95。其中数字7位索引的长度。如下建立索引:
ALTER TABLE t3 add index index_email_prev(email(7));
但是,前缀索引有一些不好的地方,1)回表率;2)覆盖索引失效。
回表率可以理解为回表的次数增加。
覆盖索引是指针对于某些特殊的查询条件,如上面的索引如果是针对于email全字段的话,select id,email
会更快一些,且只走辅助索引。
反转索引的使用
针对于某些前面分离度不高的字串,如身份证号,前6位为地区码。MySQL原生并不支持反转索引,可以在插入时将字符串反转。在查询时使用reverse
函数转换一下查询条件。
根据情况可以执行前缀索引的distinct
来判断一下是否需要加前缀索引。
hash索引的使用
针对于上面提及的身份证号,也可以使用hash索引,准确的来说是hash字段索引。因为其索引类型还是BTREE。但hash索引需要额外增加整数字段(数据类型为int unsigned)。
ALTER TABLE test3 add name_crc int UNSIGNED,ADD index index_name(name);
前面的name_crc是字段名。
在存取时可以使用MySQL的hash函数crc32()
。
在使用该索引时,因hash会有冲突,所以需要这样使用:where crc32(name)=crc('xiaoming') and name='xiaoming'
。
hash索引只支持等值查询,不支持范围、模糊查询。
hash索引如果是联合索引,则不支持任何单字段查询。
全文索引
只在引擎为MyISAM时支持。
建立语句如下:
ALTER TABLE t3 add FULLTEXT index_full(email,name);