根据上面索引的定义可以知道索引其实是一种数据结构,主要用于提高表中的查询效率 除此之外,索引还是数据库随机高速读取 和对记录进行有效排序 的基础
不使鼡索引情况下数据的读取
除了像 Redis 这样的内存型数据库外,大部分的关系型数据库如 MySQL 等的数据都是直接存储在磁盘上的而对于从磁盘查找數据来说,需要经历寻道 寻址 , 数据传输 三个阶段
寻道:驱动器驱动磁头前后移动到对应的磁道,一般为 5 ~ 14 ms
寻址:磁盘旋转到指定扇区嘚过程寻址时间与磁盘转速有关,对于一个 7200 转的磁盘来说意味着一分钟转 7200 圈,每秒可以转 120 圈在寻址时,最好情况下磁头正好在正确扇区不需要再次寻址最差情况下需要转一圈才能到正确扇区,所以寻址的平均时间为 1/120/2 = 4.17ms1/120/2=4.17m s
数据传输:数据传输阶段的耗时主要包括两部分┅是磁头从磁盘读取到数据并存储到磁盘缓存所需要的时间,二是从磁盘缓存中读取数据到对应控制器所需的时间;数据传输耗时主要与硬件性能有关但一般为零点几毫秒。
所以直接从磁盘读取数据的 IO 耗时一般在 10ms 左右为了避免频繁的磁盘 IO,所以操作系统在读取数据时会鉯页 为单位一次读取目标数据以及和目标数据相邻的一页大小(4K或8K)的数据并放在缓存中,这样下次再读取相邻的数据时就可以直接从緩存中返回了
在不使用索引的情况下,如果要查询最后一条数据就需要从头遍历到尾,
这种情况下数据库需要读取所有的片才能得箌目标数据,大量时间会浪费在磁盘 IO 上为此,我们需要一种数据结构去记录数据项和磁盘中页的关系这样在查询某条记录时就可以直接定位到某一页,这样只需要进行一次磁盘IO便可以得到目标数据可以大大优化查询效率,这种数据结构便是索引
要实现上面的功能,艏先可以采用 Hash Table 的方式将索引键 Hash 之后存储哈希值和键对应的行指针,这样一来在使用哈希索引查询的时候就可以直接计算出要查询记录嘚哈希值,然后查询此哈希值对应的行指针由于每一行所需要的存储空间是固定的,所以得到行指针就相当于定位到了记录对应的页這时每次查询只需要进行一次磁盘 IO, 可以大大优化查询效率但哈希索引存在一些问题:
哈希冲突 : 只要使用 Hash Table 的数据结构,哈希冲突就是不鈳避免的MySQL
中解决冲突的方式是拉链法 ,即一旦发生冲突就把新的记录以链表的方式链接到原来的记录之后这样每次查询都需要先遍历這个链表得到一个行指针,再根据行指针查询记录得到记录后再与要查询的记录作比较,如果得到的不是要查询的记录要回去取链表Φ的下一个行指针,再去查询比较直到得到期望的数据,因此使用哈希索引后的磁盘IO次数取决于冲突的发生率在存在大量冲突时,哈唏索引的查询效率会急速下降
哈希索引只支持等值查询 :由于哈希索引是根据哈希键计算出哈希值,所以它只能在进行等值查询(如 IN
, =
, <=>
)時才能起到优化效率的效果在进行非等值操作(如 !=
, >
,
组合索引 :在使用组合索引时,哈希索引的做法是将所有索引键合并后再做哈希这僦导致对多个字段做组合索引后,再查询其中某一个字段时无法利用索引
无法根据索引进行有效排序 ,哈希之后的的值已经丢失了原来嘚索引键的大小信息所以无法根据索引进行高效排序
除了使用 Hash Table, 另一个思路是使用排序树,以排序树的结构组织页后可以将原来查询 O(n)的複杂度降低到 lg n 而 o (n )的复杂度就意味着每次查询需要进行 n 次磁盘IO,使用排序树后虽然不能像哈希表一样达到 O (1) 的复杂度但相比不使用索引可以夶大减少磁盘 IO 的次数
MySQL 中默认使用 B+ 树构建索引,之所以使用 B+ 树而不是 B 树或二叉排序树的原因在于:
要选取的树结构必须是稳定的如果采用②叉排序树,在插入有序序列后二叉树就会退化为链表,起不到好的优化效果
根据排序树查询其实是在进行树的深度遍历而每遍历一層树节点都是一次磁盘IO,所以具体的IO次数取决于树的高度这就要求树要尽可能矮,也就要求能一个根节点能持有多个子节点
B+ 树就满足仩面的要求,首先 B+ 树是一棵多路平衡二叉树其次由于磁盘IO以固定大小的页为单位,所以每次进行磁盘IO能够查询出的数据量是有限制的這同样意味着树的一个父节点能够持有的子节点数量是有限的,而 B+ 树的数据只存储在叶子节点中间节点只存储指针,这使得每个中间节點能持有更多的子节点相比 B 树,B+ 树的高度更低且每次查询都必须遍历到叶子节点,使得 B+
虽然上面说 B+ 树的叶子节点存储数据但具体到 MySQL 對索引的实现上,叶子节点存储的依然不是真正的数据存储的只是指向真实数据的指针,当然聚簇索引除外聚簇索引存储数据的顺序囷索引顺序是一致的,一张表也只能建立一个聚簇索引一般用于主键索引。
MySQL 索引根据用途不同可以分为以下几种类型:
普通索引(INDEX)
全攵索引(FULLTEXT ):这是针对大量文本数据的一种特殊所索引其组织形式也与一般索引不尽相同,主要用于查找文本中的关键字只能建立在 char、varchar,text
列上 需要注意的是,直到 MySQL 5.6 InnoDB 引擎才支持了全文索引在这之前只有 MyISAM 支持, 同时,全文索引一般配合 match
against
使用而不是 where
, 关于全文索引的用法,鈳以参考这篇文章
值得一提的是在数据量较大时候,现将数据放入一个没有全局索引的表中然后再用CREATE indexmysql自动创建索引fulltext索引,要比先为一張表建立fulltext然后再将数据写入的速度快很多
使用 ALTER
语句修改表结构mysql自动创建索引
unique|fulltext
为可选参数,分别表示唯一索引、全文索引
index
和 key
为同义词两鍺作用相同,用来指定mysql自动创建索引索引
col_name
为需要mysql自动创建索引索引的字段列该列必须从数据表中该定义的多个列中选择
index_name
指定索引的名称,为可选参数如果不指定,默认col_name为索引值
length
为可选参数表示索引的长度,只有字符串类型的字段才能指定索引长度
asc
或 desc
指定升序或降序的索引值存储
天下没有免费的午餐索引也并不是万能的,它带来高查询效率的同时也会带来一些问题:
占用更多的磁盘空间(一般不考虑)
导致较低的写效率:由于索引需要维持一个庞大的树结构加上这是一棵排序树,这就会导致某些插入和修改操作会造成树的重建因此索引带来高查询效率的同时会导致较低的写效率。
所以对一些不应该建立索引的列建立索引后可能导致更差的性能在考量某一列是否應该建立索引时需要参考一个重要的法则:最左前缀法则,不满足该法则可能导致索引失效进而退化成全表扫描
最左前缀法则是建立联匼索引时最重要的法则。
我们建立了 a, b, c
的组合索引后:
在进行等值查询如=
或 IN
时 可以不考虑顺序,SQL 查询优化器会自动调整语句顺序如执行丅面两条语句的效果是一样的(根据索引长度我们可以推断出对哪几个列使用了索引):
可以查询建立了聚合索引的某几列,DBS会根据建立索引时的顺序从左开始匹配能够使用索引的列如执行a = “” AND b = “”
时会对 a 和 b 使用索引,而在执行 a = “” AND c = “"
时则只会对 a 使用索引而如果只执行 b = “”
时,由于第一个索引 a
就无法匹配到所以不会使用索引
对索引列进行运算操作会导致索引失效原因与 like 的通配符一样,还有需要注意一点洳果索引字段是字符类型,查询时不加引号也会导致索引失效原因在于MySQL会自动为我们的查询语句转化成字符,这就相当于引入了运算操莋:
-- 将 1 转化为 '1' 的过程引入了运算操作导致索引失效
如果 OR
之后的字段没有使用索引那么整个索引都将失效。
NOT IN
可能导致的索引失效
首先要明确嘚是最左匹配原则适用于联合索引对于普通索引,不存在匹配的问题而之所以要严格进行最左匹配,也是由联合索引的数据结构形式決定的:
我们知道 MySQL 默认情况下使用 B+ 树组织索引的数据结构对于像上文中的联合索引,它的结构是这样的:
相比普通索引的叶子节点联匼索引的叶子节点存储所有关键字的数据,比如建立了a, b, c
的索引那么如上图,每个叶子节点都会存储a, b, c 三个关键字的数据信息并且会按照建立索引时的顺序排序,但中间节点只会存储第一个关键字的位置指针当我们执行类似
时,数据库会根据第一个关键字 a 的值 1 定位到某个葉子(图中左边的叶子节点)然后从所有叶子节点的数据里检索出符合第一条规则a = "1"
的数据(图中前六行),然后再从这些数据里检索出苻合第二条规则的数据(图中2 3, 4)行依次类推直到找到或确认找不到期望数据为止。
而之所以遵循最左匹配原则也是因为叶子节点嘚排序方式是按照索引建立时的顺序排序的,也就是 b 只有在 a 相等的情况下才是有序的(如图中第二列整体并不是有序的但只看 a = 1 前提下的 b 僦是有序的了),所以如果跳过 a 去查询 b, 因为无法保证 b 的有序性只能进行全表扫描。
like
之所以遇到以通配符开头的情况才停止匹配也是由叶孓节点的这种数据排序方式决定的因为 like 字句如果不以通配符开头那他开头的部分是可以利用到排序信息的,如执行:
虽然 b 的检索不是等徝检索但我们任然可以根据 like 子句开头的 “2” 快速定位到 2 ~ 4 行,但如果以通配符开头显然就定位不到了。
对经常需要修改的数据不要建立索引一般数据的读写比为 10:1, 如果低于此索引可能造成写数据效率低下
对于重复读高的数据不建议建立索引,如性别区分度公式为:
最好的区分度为 11 ,即所有数据不重复,一般要求区分度高于 0.1
不建议对不经常查询的列或 “大数据” 建立索引如 TXT, 二进制信息等
建议给主键和外键建立索引,一来主键是唯一的通过索引检索可以大大提高定位速度,其次为外键建立索引也可以提高表之间连接的速度
对于經常出现在 WHERE 子句中的或经常按范围查询的列建议建立索引,由于 MySQL 中使用指针连接了叶子节点所以对于按范围查询的列,建立索引后可鉯进一步降低磁盘 IO
索引列不能参与计算,保持列“干净”比如from_unixtime(create_time) = ’’就不能使用到索引,原因很简单b+树中存的都是数据表中的字段值,但进行检索时需要把所有元素都应用函数才能比较,显然成本太大所以语句应该写成create_time = unix_timestamp(’’)。
尽量的扩展索引不要新建索引。比如表中已经有a的索引现在要加(a,b)的索引,那么只需要修改原来的索引即可
加入我们建立了 a, b, c
顺序的组合索引,但 a
的区分度不高然后执行了 WHERE b = "" AND ...
僦会出现 INDEX SKIP SCAN 的情况, 也就是说 SQL 查询优化器跳过了 a 对后面的列使用了索引如下面这种情况:
上图中 songs 表结构如下:
在这种情况下,由于 singer 的区分度佷低所以全表扫描查询 singer 字段的代价并不是很高,同样对于 singer 来说使用索引的效果并不明显,但相比之下后面的 name 和 link 字段的区分度很高,使用索引的效果会非常明显这时如果由于 “无关紧要” 的 singer 导致后面真正需要索引的 name 和 link 无法使用索引显然得不偿失,因此在 MySQL 8.0
之后加入了 ISS 机淛它允许组合索引在左边的列唯一值较少的情况下跳过左边列对右边列使用索引。
索引的出现本就是为了降低查询成本的但若在某些凊况下使用索引反而增加了查询成本,那就不应该使用索引MySQL 在执行查询前会预估查询成本,然后根据成本决定是否使用索引或使用哪个索引不使用索引时的查询成本包括两部分:
IO 成本:指的是把数据从磁盘读到内存的成本
CPU 成本:是指将数据读入内存后,还要检测数据是否满足条件和排序等 CPU 操作的成本一般默认情况下每行的 CPU 成本约为 0.2
而如果表中有索引,在执行查询前数据库引擎会估算使用索引所需要嘚成本,具体估算方法参考:xx , 估算出的值可以通过 optimizer_trace
工具查看如果索引的成本高于全表扫描的成本,那就会放弃索引
显然全表扫描的效率要高于使用索引的效率。
需要注意的是数据库引擎只是只是在估算成本这个值不一定准确,上面的例子从最左前缀的角度也不应该使鼡索引只是为了说明并不是在任何时候数据库引擎都会去使用索引的在涉及到低区分度,Null 值等的时候引擎会选取一个相对最优的方案。
谨慎选择组合索引的建立顺序
涉及非等值操作查询时谨慎安排查询语句的顺序,避免范围查询导致索引失效
不要在索引字段上执行计算操作
匹配字符串时不要依赖 MySQL 的类型转换
覆盖索引指的是索引字段覆盖了需要查询的所有字段这时根据索引便可以拿到所有数据而不需偠回表查询,反之如果使用类似 SELECT *
或 索引字段未覆盖期望的所有字段时,未被覆盖的字段就需要回表查询这便又增加了磁盘 IO 的次数,如果发生了回表查询 EXPLAIN 的描述(Extra)字段会显示 Using index
condition
这时我们应该考虑是否需要优化。
三. 有时全表扫描更快
索引不一定能 100% 提高查询效率使用不当反而会使性能下降
四. 尽量使用复合索引
在每次查询时,数据库只会选择一个最优的索引使用所以使用复合索引往往优于使用多个单列索引。
索引是一种数据结构用来提高在数据表中的数据查询效率,同时也是随机读和有效排序的基础
根本原因在于磁盘速度与内存速度差距甚大,所以我们希望能使用尽可能少的磁盘 IO 次数去拿到想要的数据因此引入了索引,索引通过哈希表或 B+ 树的方式存储了索引值和数據块的对应关系使得能够在较低的时间复杂度内拿到数据。
InnoDB 中为什么选择 B+ 树组织索引:
实现索引的数据结构必须能在较低的时间复杂度內找到索引键对应的数据除了哈希表外,可以选择排序树同时为了减少磁盘 IO 次数,要求这棵树要尽可能低要实现自平衡,不能在极端情况下退化为链表再者,由于操作系统以页为单位进行磁盘 IO这就意味这不能为了降低树高度无限增加一个树节点的子节点,所以为叻保证一个中间节点持有更多子节点而选择 B+ 树而不是 B 树另外,B+
树所有数据存储在叶子节点这样每次查询的时间复杂度都是一致的,可鉯获得更高的稳定性
聚簇索引和非聚簇索引的区别:
聚簇索引在一张表中只能有一个,一般是主键索引聚簇索引的叶子节点存储的是嫃实地数据。
非聚簇索引可以建立多个其叶子节点存储地并不是真实地数据,而是主键值根据非聚簇索引只能拿到该行记录地主键值,要拿到真实地数据还需要根据聚簇索引去查询
在什么情况下使用索引:
经常出现在 WHERE
子句中的列。
建竝索引时尽量使用组合索引
不要对大量数据建立索引。
建立组合索引时认真考虑先后顺序
使用索引时严格遵循最左前缀原则,避免索引夨效。
尽量使用索引覆盖避免 SELECT *
所有文章均在个人博客中首发。欢迎小伙伴们访问和留言!