Mysql索引详解。

前言

昨天面试被问到了索引,之前也看过不少关于索引的资料,但是感觉还是有很多欠缺。故结合网上一些资料将知识重新梳理一遍。

一、索引的原理

1.1 索引原理

索引的目的在于提高查询效率,与我们查阅读书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。
本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的时间变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。
数据库也是一样,但显然要复杂的多,因为不仅面临着等值查询,还有范围查询(>、<、between、in)
、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有问题呢?我们回想到字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段…这样查第250条数据,只要找到第三段就可以了,一下子去除了90%的无效数据。单如果1千万的记录呢,分成几段比较好?稍有算法基础的同学会想到搜索树,其平均复杂度是logN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的。而数据库实现比较复杂,一方面数据是保存在磁盘上的,另外一方面为了提高性能,每次又可以把部分数据读入内存计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。

1.2 磁盘IO与预读

考虑到磁盘IO是非常高昂的操作,计算机系统做了一些优化,当一次IO时,不光把当前磁盘地址的数据,而且把响铃都数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当一个计算机访问一个地址的数据的时候,与之相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。

1.2 索引的数据结构

我们需要这种数据结构能做些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,b+树应运而生。
在这里插入图片描述
如上图,是一颗b+树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点不存储真是的数据,只存储指引搜索方向的数据项,如17、35并不真是存在于数据表中。另叶节点之间被一个链表链接起来,方便顺序索引。

b树的查找过程

如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块的P2指针,内存时间因为非常短(相对于磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘快P2指针,通过指针加载磁盘快8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的B+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常高。

二、MySQL的索引实现

2.1 MyISAM索引实现

MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址,索引文件与数据分离,是一种非聚集索引。下图是MyISAM索引的原理图:
在这里插入图片描述
这里设表一共有三列,假设我们以Col1为主键,则图3是一个MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。如果我们在Col2上建立一个辅助索引,则此索引的结构如下图所示:
在这里插入图片描述同样也是一颗B+Tree,data域保存数据记录的地址。因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。

MyISAM的索引方式也叫做“非聚集”的,之所以这么称呼是为了与InnoDB的聚集索引区分。

非聚集索引数据行在物理上是无序的。

2.2 InnoDB索引实现

虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。

第一个重大区别是InnoDB的数据文件本身就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。
在这里插入图片描述
上图是InnoDB主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。

第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。例如,下图为定义在Col3上的一个辅助索引:
在这里插入图片描述
聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。

扫描二维码关注公众号,回复: 4582867 查看本文章

InonDB采用的这种聚集索引在物理上是有序的。

了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助,例如知道了InnoDB的索引实现后,就很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。再例如,用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。

三、索引与优化

3.1 选择索引的数据类型

MySQL支持很多数据类型,选择合适的数据类型存储数据对性能有很大的影响。通常来说,可以遵循以下一些指导原则:

(1)越小的数据类型通常更好:越小的数据类型通常在磁盘、内存和CPU缓存中都需要更少的空间,处理起来更快。
(2)简单的数据类型更好:整型数据比起字符,处理开销更小,因为字符串的比较更复杂。在MySQL中,应该用内置的日期和时间数据类型,而不是用字符串来存储时间;以及用整型数据类型存储IP地址。
(3)尽量避免NULL:应该指定列为NOT NULL,除非你想存储NULL。在MySQL中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值。

3.2 选择标识符
选择合适的标识符是非常重要的。选择时不仅应该考虑存储类型,而且应该考虑MySQL是怎样进行运算和比较的。一旦选定数据类型,应该保证所有相关的表都使用相同的数据类型。
(1) 整型:通常是作为标识符的最好选择,因为可以更快的处理,而且可以设置为AUTO_INCREMENT。
(2) 字符串:尽量避免使用字符串作为标识符,它们消耗更高的空间,处理起来也较慢。而且,通常来说,字符串都是随机的,所以它们在索引中的位置也是随机的,这会导致页面分裂、随机访问磁盘,聚簇索引分裂(对于使用聚簇索引的存储引擎)。

3.3 哪些情况需要创建索引
(1) 主键自动建立唯一索引
(2) 频繁作为查询查询条件的字段应该创建索引
(3) 查询中与其它表关联的字段,外键关系建立索引
(4) 单键/组合索引的选择问题(在高并发下倾向创建组合索引)
(5) 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
(6) 查询中统计或者分组字段

3.4 哪些情况不要创建索引
(1) 表记录太少
(2) 经常增删改的表(因为不仅要保存数据,还要保存一下索引文件)
(3) 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。
(4) 频繁更新的字段不适合创建索引
(5) where条件里用不到的字段不创建索引

3.5查询语句的优化

避免索引失效
  1.最佳左前缀法则:如果索引了多列,要尊守最左前缀法则,指的是查询从索引的最左前列开始并且不跳过索引中的列。
  2.不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。
  3.存储引擎不能使用索引中范围条件右边的列。
  如这样的sql: select * from user where username=‘123’ and age>20 and phone=‘1390012345’,其中username, age, phone都有索引,只有username和age会生效,phone的索引没有用到。
  4.尽量使用覆盖索引(只访问索引的查询(索引列和查询列致)),如select age from user减少select *
  5.mysql在使用不等于(!= 或者 <>)的时候无法使用索引会导致全表扫描。
  6.is null, is not null 也无法使用索引。
  7.like 以通配符开头(‘%abc…’)mysql索引失效会变成全表扫描的操作。
  所以最好用右边like ‘abc%’。如果两边都要用,可以用select age from user where username like ‘%abc%’,其中age是索引列
  假如index(a,b,c), where a=3 and b like ‘abc%’ and c=4,a能用,b能用,c不能用
  8.字符串不加单引号索引失效
  9.少用or,用它来连接时会索引失效
  10.尽量避免子查询,而用join

猜你喜欢

转载自blog.csdn.net/f191501223/article/details/85115385