MySQL 回表查询

1、什么是回表查询?

这先要从InnoDB的索引实现说起,InnoDB有两大类索引:

聚集索引(clustered index)
普通索引(secondary index)

2、聚集索引和普通索引有什么差异?

InnoDB聚集索引的叶子节点存储行记录,因此, InnoDB必须要有,且只有一个聚集索引:

如果表定义了PK,则PK就是聚集索引;

如果表没有定义PK,则第一个not NULL unique列是聚集索引;

否则,InnoDB会创建一个隐藏的row-id作为聚集索引;

所以PK查询非常快,直接定位行记录。

InnoDB普通索引的叶子节点存储主键值。

注意,不是存储行记录头指针,MyISAM的索引叶子节点存储记录指针。

举个栗子,不妨设有表:

t(id PK, name KEY, sex, flag); # id是聚集索引,name是普通索引。

# 表中有四条记录:
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B

两个B+树索引分别如上图:

id为PK,聚集索引,叶子节点存储行记录;
name为KEY,普通索引,叶子节点存储PK值,即id;

3、普通索引的查询过程是怎么样的呢?

从上图可知普通索引无法直接定位行记录,通常情况下,需要扫码两遍索引树。
例如:

select * from t where name='lisi';

是如何执行的呢?

粉红色路径,需要扫码两遍索引树:

先通过普通索引定位到主键值id=5;
在通过聚集索引定位到行记录;

这就是所谓的回表查询,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。
回表查询概念引出了数据查询的优化概念:索引覆盖。
具体看下面内容

4、什么是索引覆盖

查询条件走索引,且查询字段包含于走索引的查询条件字段中;
只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快;
在explainz执行计划中的表现为Extra字段为Using index时,能够触发索引覆盖。

5、索引覆盖的性能提升效果

表结构:

CREATE TABLE `users` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email_verified_at` timestamp NULL DEFAULT NULL,
  `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=202001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

表内数据行数为202001行

以下面sql语句为例:

# 不能索引覆盖,需要回表
select * from users where email = '[email protected]';

# 索引覆盖,不需回表
select email from users where email = '[email protected]';

执行时间比较:

执行计划比较:

explain select * from users where email = '[email protected]';
explain select email from users where email = '[email protected]';

6、如何实现索引覆盖?

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

索引覆盖时执行计划Extra项会显示:Using index。

当不能索引覆盖需要回表时,执行计划Extra选线显示:Using index condition;这个特性是5.6版本后新加的,我执行时发现回表时,执行计划Extra选线显示为null,我的mysql是5.7版本,不知道为什么没有显示Using index condition。

注意: 不能为了索引覆盖而建立太大的复合索引,索引本身也是消耗资源的。