MYSQL索引详解及索引优化、分析
1.什么是索引
索引在MySQL中是比较常见的,索引就相当于我们看书的目录,它是帮助MySQL高效获取数据的一种数据结构,主要用来提高数据的检索效率,减少IO成本,同时通过索引对数据进行排序,降低排序成本。
2.索引的作用
-
加快查询速度:通过索引快速定位数据。
-
保证数据唯一性:如主键和唯一索引。
-
优化排序和分组:索引可以加速
ORDER BY
和GROUP BY
操作。
3.索引的分类
- 按「数据结构」分类:B+tree索引、Hash索引、Full-text索引。
- 按「物理存储」分类:聚簇索引(主键索引)、二级索引(辅助索引)。
- 按「字段特性」分类:主键索引、唯一索引、普通索引、前缀索引。
- 按「字段个数」分类:单列索引、联合索引。
聚簇索引主要是指数据和索引放到一块,B+树叶子节点报存了整行数据。
非聚簇索引是数据与索引分开的,B+树叶子节点只保存主键值。
4.索引的结构
在创建表时,InnoDB 存储引擎会根据不同的场景选择不同的列作为索引:
- 如果有主键,默认会使用主键作为聚簇索引的索引键(key);
- 如果没有主键,就选择第一个不包含 NULL 值的唯一列作为聚簇索引的索引键(key);
- 在上面两个都没有的情况下,InnoDB 将自动生成一个隐式自增 id 列作为聚簇索引的索引键(key);
其它索引都属于辅助索引(Secondary Index),也被称为二级索引或非聚簇索引。创建的主键索引和二级索引默 认使用的是 B+Tree 索引。
数据库的索引和数据都是存储在硬盘的,我们可以把读取一个节点当作一次磁盘I/O操作。
重点:B+Tree存储千万级的数据只需要3-4层高度就可以满足,意味着从千万级别的表查询目标数据最多需要3-4次磁盘的I/O,所以B+Tree相对于B树和二叉树来说,最大优势在于查询效率很高,因为即使数据量很大,磁盘I/O仍维持在3-4次。
5.主键索引的B+Tree和二级索引的B+Tree的区别
1)主键索引的 B+Tree 的叶子节点存放的是实际数据,所有完整的用户记录都存放在主键索引的 B+Tree 的叶子节点里;
2) 二级索引的 B+Tree 的叶子节点存放的是主键值,而不是实际数据。
6.什么是回表查询
用二级索引查数据时,会先检二级索引中的 B+Tree 的索引值(商品编码,product_no),找到对应的叶子节点,然后获取主键值,然后再通过主键索引中的 B+Tree 树查询到对应的叶子节点,然后获取整行数据。这个过程叫「回表」,也就是说要查两个 B+Tree 才能查到数据。
不过,当查询的数据是能在二级索引的 B+Tree 的叶子节点里查询到,这时就不用再查主键索引查,比如下面这条查询语句:
select id from product where product_no = '0002';
这种在二级索引的 B+Tree 就能查询到结果的过程就叫作「覆盖索引」,也就是只需要查一个 B+Tree 就能找到数据。
7.为什么MySQL 默认的存储引擎 InnoDB 采用的是 B+ 作为索引的数据结构,而不是B树
原因有:
- B+ 树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比于既存索引又存记录的 B 树,B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少。
- B+ 树有大量的冗余节点(所有非叶子节点都是冗余索引),这些冗余索引让 B+ 树在插入、删除的效率都更高,比如删除根节点的时候,不会像 B 树那样会发生复杂的树的变化;
- (范围查询)B+ 树叶子节点之间用链表连接了起来,有利于范围查询,而 B 树要实现范围查询,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘 I/O 操作,范围查询效率不如 B+ 树。
8.索引为什么会失效的六种情况
今天给大家介绍了 6 种会发生索引失效的情况:
1)当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx% 这两种方式都会造成索引失效;
原因:因为索引 B+ 树是按照「索引值」有序排列存储的,只能根据前缀进行比较,而%在左边模糊匹配时无法识别左边是对于哪个值。
2)当我们在查询条件中对索引列使用函数,就会导致索引失效。
原因:因为索引保存的是索引字段的原始值,而不是经过函数计算后的值,自然就没办法走索引了。
3)当我们在查询条件中对索引列进行表达式计算,也是无法走索引的。
原因:因为索引保存的是索引字段的原始值,而不是经过表达式计算后的值,自然就没办法走索引了。
4)MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。如果字符串是索引列,而条件语句中的输入参数是数字的话,那么索引列会发生隐式类型转换,由于隐式类型转换是通过 CAST 函数实现的,等同于对索引列使用了函数,所以就会导致索引失效。
5)联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
原因:在联合索引的情况下,数据是按照索引第一列排序,第一列数据相同时才会按照第二列排序。
也就是说,如果我们想使用联合索引中尽可能多的列,查询条件中的各个列必须是联合索引中从最左边开始连续的列。如果我们仅仅按照第二列搜索,肯定无法走索引。
6)在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。
原因:这是因为 OR 的含义就是两个只要满足一个即可,因此只有一个条件列是索引列是没有意义的,只要有条件列不是索引列,就会进行全表扫描。
9.索引优化的方法
1)前缀索引优化
前缀索引顾名思义就是使用某个字段中字符串的前几个字符建立索引,那我们为什么需要使用前缀来建立索引呢? 使用前缀索引是为了减小索引字段大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的字段作为索引时,使用前缀索引可以帮助我们减小索引项的大小。
不过,前缀索引有一定的局限性,例如:
1.order by 就无法使用前缀索引;
2.无法把前缀索引用作覆盖索引;
2)覆盖索引优化
覆盖索引是指 SQL 中 query 的所有字段,在索引 B+Tree 的叶子节点上都能找得到的那些索引,从二级索引中查询得到记录,而不需要通过聚簇索引查询获得,可以避免回表的操作。
假设我们只需要查询商品的名称、价格,有什么方式可以避免回表呢?
我们可以建立一个联合索引,即「商品ID、名称、价格」作为一个联合索引。如果索引中存在这些数据,查询将不会再次检索主键索引,从而避免回表。
所以,使用覆盖索引的好处就是,不需要查询出包含整行记录的所有信息,也就减少了大量的 I/O 操作。
3)主键索引最好是递增的
InnoDB 创建主键索引默认为聚簇索引,数据被存放在了 B+Tree 的叶子节点上。
也就是说,同一个叶子节点内的各个数据是按主键顺序存放的,因此,每当有一条新的数据插入时,数据库会根据主键将其插入到对应的叶子节点中。
如果我们使用自增主键,那么每次插入的新数据就会按顺序添加到当前索引节点的位置,不需要移动已有的数据,当页面写满,就会自动开辟一个新页面。因为每次插入一条新记录,都是追加操作,不需要重新移动数据,因此这种插入数据的方法效率非常高。
如果我们使用非自增主键,由于每次插入主键的索引值都是随机的,因此每次插入新的数据时,就可能会插入到现有数据页中间的某个位置,这将不得不移动其它数据来满足新数据的插入,甚至需要从一个页面复制数据到另外一个页面,我们通常将这种情况称为页分裂。页分裂还有可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率。
4)索引最好设置为 NOT NULL
为了更好的利用索引,索引列要设置为 NOT NULL 约束。有两个原因:
第一原因:索引列存在 NULL 就会导致优化器在做索引选择的时候更加复杂,更加难以优化,因为可为 NULL 的列会使索引、索引统计和值比较都更复杂,比如进行索引统计时,count 会省略值为NULL 的行。
第二个原因:NULL 值是一个没意义的值,但是它会占用物理空间,所以会带来的存储空间的问题,因为 InnoDB 存储记录的时候,如果表中存在允许为 NULL 的字段,那么行格式 (opens new window)中至少会用 1 字节空间存储 NULL 值列表,如下图的紫色部分:
10.索引的最左前缀匹配原则(联合索引)
MYSQL索引的最左前缀匹配原则是指在使用联合索引时,查询条件必须从索引的最左侧开始匹配。
只有第一个索引匹配成功了才会用第二个索引匹配,以此类推。
1)索引下推:是一种减少回表查询的,提高查询效率的技术,即将部分数据下推到引擎层进行过滤,减少从表中读取的数据行。
MYSQL5.6版本以后有个索引下推的优化。
例如有联合索引(a,b,c)然后有一条查询语句:
select * from user where a = 1,c = 3;
在MYSQL5.6版本之前,在根据索引a = 1过滤完数据后,就会回到server层进行回表查询
而MYSQL5.6版本之后,有了索引下推功能,在根据索引a = 1过滤完数据后,会将c=3下推到给存储引擎层进行过滤,利用c=3过滤掉不符合的数据,然后再返回给server层,大大增加了查询效率。
2)跳过扫描范围访问(Skip Scan Range Access Method)
在MYSQL 8.0.13引入了Skip Scan Range Access Method
,在一定条件下,利用范围查询替代了全表扫描的发生。
例如有联合索引(a,b)然后有一条查询语句:
select * from user where b = 3;
在MYSQL 8.0.13前,就会进行全表扫描。
在MYSQL 8.0.13后,由于引入了Skip Scan Range Access Method
,当索引最左字段a的基数很小时(比如只有a = 1和a = 2的情况),会将a = 1和a = 2 分别拼接到查询条件中(即where条件后)从而实现,最左匹配原则。
3)面试题:假如你有一个查询很慢,且你已经确定查询使用了一个复合索引,你会如何诊断并优化这个查询?
答:首先,可以使用EXPLAIN语句来查看查询的执行计划,确认是否使用了正确的的索引。如果发现查询没有充分利用索引,可以查询是否存在违犯最左匹配原则的情况,调整查询条件的顺序,以及其他优化措施,如:减少返回的列数,使用索引覆盖,前缀索引优化等。
4)EXPLAIN语句
EXPLAIN语句用于分析 SELECT
语句的执行计划,帮助开发者了解查询是如何执行的,可以查看Mysql如何执行查询,包括哪些索引被使用、查询的执行顺序、扫描的行数等等。
EXPLAIN SELECT * FROM users WHERE age > 30;
5)SQL语句执行很慢,如何分析的呢?
采用MySQL自带的分析工具Explain,加在查询语句前即可。
1)执行命令后查看key和key_len检查是否命中索引。
2)通过type字段检查sql是否能进一步优化,是否出现all(全表扫描)和index(全索引扫描)
3)通过extra判断是否出现了回表情况(using index condition)
5)在mysql中如何查看sql执行时间?
在MySQL中,profiling
功能可以帮助你分析SQL查询的性能。默认情况下,profiling
是关闭的,你需要通过以下命令将其开启:
1.SET profiling = 1;
2.执行要查看时间的sql :select * from user;
3.如何调用:SHOW PROFILES;
查看执行时间。
本文参考小林coding。