MySQL的覆盖索引与回表和order by

  • 一、聚集索引
  • 二、覆盖索引与回表
  • 1、表结构
  • 2、覆盖索引
  • 3、回表
  • 三、辅助索引
  • 四、联合索引
  • 五、哪些场景可以利用索引覆盖来优化SQL
  • 1、 全表count查询优化
  • 2、 列查询回表优化
  • 3、分页查询
  • 六、其他索引优化
  • 1、带条件查询索引优化
  • 2、一般索引优化
  • 3、order by使用
  • 4、优化数据访问
  • 七、小结
  • 1、索引的优点
  • 2、索引的使用条件
  • 3、索引查询类型



一、聚集索引

  • 聚集索引就是按照每张表的主键构造一棵B+树,同时叶子节点中存放的即为整张表的行记录数据。
  • 如果定义了主键,InnoDB会自动使用主键来创建聚集索引。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替主键。如果没有唯一的非空索引,InnoDB会隐式定义一个主键来作为聚集索引。

二、覆盖索引与回表

1、表结构

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `sex` varchar(255) DEFAULT NULL,
  `address` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2、覆盖索引

explain的输出结果Extra字段为Using index时,能够触发索引覆盖。
如何实现索引覆盖

常见的方法是:将被查询的字段,建立到联合索引里去。

3、回表

(1)先通过普通索引定位到主键值;
(2)再通过聚集索引定位到行记录;

这就是所谓的 回表查询 ,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。

不管是SQL-Server官网,还是MySQL官网,都表达了:只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。

三、辅助索引

辅助索引,也叫非聚集索引。和聚集索引相比,叶子节点中并不包含行记录的全部数据。叶子节点除了包含键值以外,每个叶子节点的索引行还包含了一个书签(bookmark),该书签用来告诉InnoDB哪里可以找到与索引相对应的行数据。

辅助索引可以理解成字典按偏旁去查字。

四、联合索引

联合索引是指对表上的多个列进行索引。

联合索引也是一棵B+树,其键值数量大于等于2。键值都是排序的,通过叶子节点可以逻辑上顺序的读出所有数据。数据(1,1)(1,2)(2,1)(2,4)(3,1)(3,2)是按照(a,b)先比较a再比较b的顺序排列。

联合索引的第二个好处是对第二个键值已经做了排序。

五、哪些场景可以利用索引覆盖来优化SQL

1、 全表count查询优化

psql聚合索引 mysql 聚合索引优化_b树


添加索引后

psql聚合索引 mysql 聚合索引优化_数据库_02

2、 列查询回表优化

select id,name,sex … where name=‘张三’;

将单列索引(name)升级为联合索引(name, sex),即可避免回表。

psql聚合索引 mysql 聚合索引优化_mysql_03


psql聚合索引 mysql 聚合索引优化_psql聚合索引_04

3、分页查询

select id,name,sex … order by name limit 500,100;
将单列索引(name)升级为联合索引(name, sex),也可以避免回表。

六、其他索引优化

1、带条件查询索引优化

  1. table: sql所查询的表名
  2. type: 结果值从好到坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

一般来说,得保证查询至少达到range级别,最好能达到ref,否则就可能会出现性能问题

  1. possible_keys: sql可能用到的索引
  2. key: sql实际用到的索引,如果是Null,说明没有用到索引
  3. rows: MySQL认为执行查询时必须检查的行数

psql聚合索引 mysql 聚合索引优化_数据库_05


psql聚合索引 mysql 聚合索引优化_mysql_06


psql聚合索引 mysql 聚合索引优化_psql聚合索引_07


psql聚合索引 mysql 聚合索引优化_psql聚合索引_08


psql聚合索引 mysql 聚合索引优化_b树_09

2、一般索引优化

  1. 独立的列
    在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引:
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
  1. 多列索引
    在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引:
SELECT film_id, actor_ id FROM sakila.film_actor
WHERE actor_id = 1 AND film_id = 1;
  1. 索引列的顺序
    让选择性最强的索引列放在前面。
    索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,每个记录的区分度越高,查询效率也越高。
    例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。
SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,
COUNT(*)
FROM payment;

   staff_id_selectivity: 0.0001
customer_id_selectivity: 0.0373
               COUNT(*): 16049
  1. 前缀索引
    对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。
    前缀长度的选取需要根据索引选择性来确定。
    建立前缀索引
alter table music add index music_index(name(2));
  1. 覆盖索引
    索引包含所有需要查询的字段的值。
    索引通常远小于数据行的大小,只读取索引能大大减少数据访问量。
    一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
    对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。

3、order by使用

当排序的数据很大,超过 sort_buffer 的大小,那么就需要文件排序,文件排序涉及到分批排序与合并,很耗时,造成这个问题的根本原因是 sort_buffer 不够用

mysql> show variables like 'max_length_for_sort_data';
+--------------------------+-------+
| Variable_name          | Value |
+--------------------------+-------+
| max_length_for_sort_data | 1024  |
+--------------------------+-------+
  1. 对于 order by 没有用到索引的时候,这时 explain 中 Extra 字段大概是会出现 using filesort 字眼
  2. 出现 using filesort 的时候也不用太慌张,如果本身数据量不大,比如也就几十条数据,那么在 sort buffer 中使用快排也是很快的
  3. 如果数据量很大,超过了 sort buffer 的大小,那么是要进行临时文件排序的,也就是归并排序,这部分是由 MySQL 优化器决定的
  4. 如果查询的字段很多,想要尽量避免使用临时文件排序,可以尝试设置下 max_length_for_sort_data 字段的大小,让其小于所有查询字段长度的总和,这样放入或许可以避免,但是会多一次回表操作
  5. 实际业务中,我们也可以给经常要查询的字段组合建立个联合索引,这样既不用回表也不需要单独排序,但是联合索引会占用更多的存储和开销
  6. 大量数据查询的时候,尽量分批次,提前 explain 来观察 sql 的执行计划是个不错的选择。

联合索引虽然可以解决这种问题,但是在实际应用中切不可盲目建立,要根据实际的业务逻辑来判断是否需要建立,如果不是经常有类似的查询,可以不用建立,因为联合索引会占用更多的存储空间和维护开销。

4、优化数据访问

减少请求的数据量

  • 只返回必要的列:最好不要使用 SELECT * 语句。
  • 只返回必要的行:使用 LIMIT 语句来限制返回的数据。
  • 缓存重复查询的数据:使用缓存可以避免在数据库中进行查询,特别在要查询的数据经常被重复查询时,缓存带来的查询性能提升将会是非常明显的。

减少服务器端扫描的行数

  • 最有效的方式是使用索引来覆盖查询。

分解大连接查询
将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有

  • 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。
  • 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。
  • 减少锁竞争;
  • 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可伸缩。
  • 查询本身效率也可能会有所提升。例如下面的例子中,使用 IN() 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。

七、小结

1、索引的优点

  1. 大大减少了服务器需要扫描的数据行数。
  2. 帮助服务器避免进行排序和分组,以及避免创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和
  3. GROUP BY 操作。临时表主要是在排序和分组过程中创建,不需要排序和分组,也就不需要创建临时表)。
    将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起)。

2、索引的使用条件

  1. 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效;
  2. 对于中到大型的表,索引就非常有效;
  3. 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。

3、索引查询类型

Type

  1. system:表只有一行,这是一个 const type 的特殊情况
  2. const:使用主键或者唯一索引进行查询的时候只有一行匹配
  3. ref:使用非唯一索引
  4. range:使用主键、单个字段的辅助索引、多个字段的辅助索引的最后一个字段进行范围查询
  5. index:和all的区别是扫描的是索引树
  6. all:扫描全表