提高查询性能的秘密:深入剖析聚集、辅助、覆盖和联合索引

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 提高查询性能的秘密:深入剖析聚集、辅助、覆盖和联合索引


在数据库中,索引是提高查询效率的重要手段之一。而聚集索引、辅助索引、覆盖索引和联合索引是常用的一些索引类型。

首先,聚集索引是一种按照主键顺序排列的索引,它将数据页中的行记录按主键值进行排序,使得相近的记录在物理存储上也是相邻的。因此,聚集索引可以实现较快的检索和范围查询。每个数据页通过一个双向链表进行链接,同时存放完整的行记录。举个例子,如果有一张订单表,主键为订单号,那么聚集索引就可以将不同订单按照订单号排序,在进行查询或者范围查询时可以快速定位到相应订单。

接着,辅助索引不是按照主键值进行排序的,而是将索引列的值作为键值存储,并指向聚集索引中相应行记录的位置。因此,辅助索引可以在聚集索引的基础上提供更多的检索方式和查询条件,例如在一张员工表中,辅助索引可以按照员工姓名进行排序,提供姓名的检索和查询。此外,辅助索引叶子节点中不包含完整的行记录,而是包含一个书签,用来指向相应的行记录,以加快查询速度。

其次,覆盖索引是指辅助索引中已经包含了需要查询的所有字段,在查询时就不需要再去聚集索引中查找相应的行记录了。这样的话可以减少IO操作,提高查询效率。举个例子,如果有一张员工表,辅助索引包含员工姓名和薪资两个字段,而查询仅需要查询员工姓名和薪资,那么辅助索引就可以提供全部需要的字段信息,无需再去聚集索引中查找。

再次,联合索引是对表上的多个列进行索引,联合索引中的键值都是排序的。联合索引的好处在于可以避免多建索引带来的开销。例如,如果建立一个(a,b,c)的复合索引,实际上就建立了(a),(a,b),(a,b,c)三个索引。这样做不仅可以避免建立过多的索引,同时减少了写操作和磁盘空间的开销。此外,联合索引还可以避免filesort排序,这是一种将结果集按照指定顺序排序的过程,用于优化查询性能,但是会增加查询的负担。联合索引可以通过叶子节点以顺序的方式读取数据,避免了filesort排序的开销,提高了查询效率。

🍊 聚集索引

聚集索引,听起来好像很高大上的样子,其实它就是一种能够让查询更快的技术。我们先来看看什么是InnoDB存储引擎表,它其实就是一种索引组织表,也就是说,在这种表中,数据是按照主键的顺序进行存放的。这样做有什么好处呢?可以让查询的速度更快!

而聚集索引,顾名思义,就是针对每一张表的主键构建一颗B+树,这样就可以在叶子节点上存储整张表的行记录数据。聚集索引的叶子节点被称为数据页,每个数据页之间是通过双向链表进行链接的。因为每张表只能拥有一个聚集索引,所以在多数情况下查询优化器会倾向于采用聚集索引。

聚集索引有什么好处呢?首先它能够在B+树索引的叶子节点上直接找到数据,这样就可以让查询的速度更快。其次,由于定义了数据的逻辑顺序,它对于主键的排序查找和范围查找速度非常快。而且,聚集索引还可以让我们在进行范围查询时,通过叶子节点的上层中间节点得知页的范围,直接读取数据页即可,这样也能够让查询速度更快。

接下来,我们来看几个例子,帮助大家更好地理解聚集索引的应用。

假设我们有一个注册用户的表,需要查找最后注册的10位用户。我们可以使用如下语句进行查询:SELECT * FROM Profile ORDER BY id LIMIT 10; 由于B+树索引是双向链表的,所以我们可以快速找到最后一个数据页,并取出10条记录。需要注意的是,在这个查询过程中,虽然使用了ORDER BY对主键id记录进行排序,但是实际上并没有进行所谓的filesort操作,而这就是因为聚集索引的特点。

再举个例子,如果我们需要查找主键某一范围内的数据,我们可以使用如下语句进行查询:SELECT * FROM Profile where id>1 and id < 100; 这种查询就是范围查询,而聚集索引正是能够让这种查询更加高效。

🍊 辅助索引

辅助索引是另一种不同的索引类型,也被称为非聚集索引。与聚集索引不同,辅助索引的叶子节点并不包含行记录的全部数据。而是包含键值以及一个书签,这个书签指向另一个存储位置,告诉数据库在哪里可以找到与该索引相对应的行数据。

举个例子来说明这一点。假设我们有一个存储顾客信息的数据库表,其中的主键是客户ID,而我们还想要按客户姓名来进行搜索。那么我们可以使用辅助索引来加速此类查询。辅助索引会通过客户姓名来建立索引,它的叶子节点会存储客户姓名以及指向主键索引的书签。当我们查询一个客户的信息时,数据库会通过辅助索引找到对应的主键,然后再通过主键索引来找到该客户的全部信息。

辅助索引的存在并不会影响数据在聚集索引中的组织,因此每张表上可以有多个辅助索引。当我们使用辅助索引来查找数据时,数据库会遍历辅助索引并通过叶级别的指针获得指向主键索引的主键,然后再通过主键索引来找到一个完整的行记录。

让我们再来看一下之前的例子。如果我们在一张存储顾客信息的表中有一个辅助索引来支持按客户姓名的查询。假设我们有一个高度为3的辅助索引树,我们需要遍历这棵树3次才能找到对应的主键。如果聚集索引树的高度也为3,那么我们还需要对聚集索引树进行3次查找,最终才能找到一条完整的记录。因此,我们总共需要6次逻辑IO访问才能得到我们需要的数据。

再来看一个示例。假设我们有一个存储产品信息的数据库表,其中的主键是产品ID。我们想要按照产品类别和价格来进行查询。我们可以创建一个辅助索引来支持这类查询。该辅助索引将产品类别和价格作为关键字,而它的叶子节点中包含了指向主键索引的书签。当我们查询某个产品类别和价格的时候,数据库会遍历辅助索引树找到相应的主键,然后再通过主键索引来找到完整的行记录。

🍊 覆盖索引

覆盖索引:减少IO操作的好帮手

覆盖索引是一种针对辅助索引的优化方式,在InnoDB存储引擎中被广泛应用,可以大大减少查询所需的IO操作,提高查询的效率。下面我们详细介绍什么是覆盖索引,以及什么情况下优化器会选择使用覆盖索引。

🎉 覆盖索引是什么?

一般情况下,当我们在进行SELECT语句查询操作时,MySQL会使用B+树索引来加速查询。B+树索引是一种常见的数据结构,它将数据按照索引字段的值建立一棵树,查询时只需遍历这棵树就可以快速找到目标数据。

在查询时,MySQL通过B+树索引查找到目标数据所在的页,然后从磁盘读取该页数据。但这时可能会出现一种情况:在目标数据所在的页中,还有很多其他的数据,而我们只需要其中的一部分数据,这样就导致了不必要的IO操作。例如,我们有一个表buy_log,包含了用户id和购买日期两个字段,创建如下的两个索引:

ALTER TABLE buy_log ADD KEY (userid);
ALTER TABLE buy_log ADD KEY (userid,buy_date);

那么如果我们执行如下的查询语句:

SELECT userid FROM buy_log WHERE buy_date='2022-05-01';

MySQL首先通过(userid,buy_date)联合索引定位到了目标数据所在的页,但是这个索引包含了两个字段,而我们只需要查询其中的一个字段userid,因此MySQL还是需要从磁盘上读取整个页的数据才能获取到最终结果。如果这样的查询操作比较频繁,将会耗费大量的IO操作,影响系统的性能。

覆盖索引的出现,就是为了解决这个问题。所谓覆盖索引,是指辅助索引中包含了查询要用到的所有字段。例如,在上述的例子中,如果我们将查询语句改为:

SELECT PRIMARY KEY, userid FROM buy_log WHERE buy_date='2022-05-01';

这时,MySQL可以直接从(userid,buy_date)联合索引中获取到所有的查询结果,因为辅助索引中已经包含了查询要用到的所有字段(PRIMARY KEY和userid),不需要再去读取聚簇索引中的数据。这样就可以避免不必要的IO操作。

需要注意的是,覆盖索引并不是所有的查询操作都可以使用的,只有在查询所需的字段都包含在辅助索引中时,才能使用覆盖索引。当然,使用覆盖索引并不能解决所有的性能问题,对于一些复杂的查询操作,还需要使用其他的优化方法。

🎉 什么情况下优化器会选择使用覆盖索引?

我们已经介绍了覆盖索引的概念和优点,接下来我们将详细介绍什么情况下优化器会选择使用覆盖索引。

📝 1. 查询操作只需要用到辅助索引中的字段

当我们查询的条件只包含辅助索引中的字段时,MySQL会尽可能使用覆盖索引来加速查询。所谓辅助索引,即非聚集索引,也称为二级索引,是相对于聚集索引(也称为主键索引)而言的。辅助索引中包含了索引字段以及主键字段的值,可以说是聚集索引的一个附属物。在InnoDB存储引擎中,辅助索引叶子节点中会包含主键字段的值,这样就能够通过辅助索引查询到目标数据的主键值,并且可以直接从辅助索引中获取到所有的查询结果,从而避免不必要的IO操作。

例如,我们有一个表buy_log,包含了用户id和购买日期两个字段,创建如下的两个索引:

ALTER TABLE buy_log ADD KEY (userid);
ALTER TABLE buy_log ADD KEY (userid,buy_date);

那么,下面这四个查询操作都可以使用覆盖索引来加速查询:

SELECT userid FROM buy_log WHERE buy_date='2022-05-01';
SELECT PRIMARY KEY, userid FROM buy_log WHERE buy_date='2022-05-01';
SELECT userid FROM buy_log WHERE userid=1 AND buy_date='2022-05-01';
SELECT PRIMARY KEY, userid FROM buy_log WHERE userid=1 AND buy_date='2022-05-01';

因为这些查询操作只需要用到辅助索引中的字段userid和buy_date,可以直接从辅助索引中获取到查询结果,不需要读取聚簇索引中的数据。

📝 2. 进行统计操作时,可以利用覆盖索引加速查询

当我们进行COUNT、AVG、SUM等统计操作时,如果辅助索引中包含了所有的统计字段,那么MySQL就会选择使用覆盖索引来加速查询,因为这样可以避免不必要的IO操作,提高查询效率。

例如,对于上面的buy_log表,如果我们要查询所有记录的数量,可以使用以下查询语句:

SELECT COUNT(*) FROM buy_log;

此时,InnoDB存储引擎并不会选择通过查询聚集索引来进行统计。由于buy_log表上还有辅助索引,而辅助索引远小于聚集索引,选择辅助索引可以减少IO操作。

📝 3. 覆盖索引的查询效率优于从聚簇索引中查询

当使用覆盖索引查询的效率要高于从聚簇索引中查询时,MySQL会优先选择使用覆盖索引。

例如,我们有一个表orders,其中包含了订单编号、订单金额和下单日期三个字段,创建了如下的索引:

CREATE TABLE orders (
  order_number INT NOT NULL,
  order_amount FLOAT NOT NULL,
  order_date DATE NOT NULL,
  PRIMARY KEY (order_number),
  KEY order_date (order_date)
);

那么,我们执行以下两种查询语句:

SELECT order_number FROM orders WHERE order_date='2022-05-01';
SELECT order_amount FROM orders WHERE order_number=1001;

第一种查询语句中,WHERE条件包含了辅助索引order_date中的字段,而order_number字段在辅助索引中也有相应的值,因此可以使用覆盖索引来加速查询。

第二种查询语句中,WHERE条件包含了聚簇索引primary key中的字段order_number,但我们需要查询的是order_amount字段,如果采用从聚簇索引中查询的方式,需要将整个数据行都读取出来,而如果使用覆盖索引,只需要读取辅助索引中的数据即可,因此查询效率会更高。

综上所述,覆盖索引是一种非常有效的优化方式,可以大大减少不必要的IO操作,提高查询效率。但需要注意的是,并不是所有的查询操作都可以使用覆盖索引,只有在满足特定条件时,MySQL才会选择使用覆盖索引来加速查询。

🍊 联合索引

联合索引:让查询更快,让数据库更高效

在数据库中,索引是非常重要的部分,因为它能够提高数据库的查询效率。而联合索引又是其中一种比较重要的索引,在对表中的多列进行索引时,它能够让查询更快,让数据库更高效。本文将详细介绍什么是联合索引、如何使用联合索引以及它的优缺点等内容。

🎉 什么是联合索引?

联合索引,指的是对表上的多个列进行索引。通常在设计数据库表的时候,我们会根据表的结构和需求来选择哪些列需要进行索引,而当需要同时对多个列进行筛选和排序时,就需要使用联合索引。

比如,在一个购物网站的数据库中,有一个购买记录的表(buy_log),其中包含用户ID(userid)和购买日期(buy_date)两个字段。如果需要根据用户ID来查询购买记录,并按购买日期降序排列,那么就需要使用联合索引对userid和buy_date进行索引,以提高查询效率。

🎉 如何使用联合索引?

假设有以下购买记录的表(buy_log):

CREATE TABLE buy_log(
userid INT UNSIGNED NOT NULL,
goods_id INT UNSIGNED NOT NULL,
buy_date DATE
)ENGINE=InnoDB;

为了提高查询效率,我们需要对用户ID(userid)、商品ID(goods_id)和购买日期(buy_date)这三个字段进行索引。那么我们可以先对userid进行单列索引,再对(userid,goods_id,buy_date)这个联合索引进行索引,代码如下:

ALTER TABLE buy_log ADD KEY (userid);
ALTER TABLE buy_log ADD KEY (userid,goods_id,buy_date);

这样,当我们需要查询商品ID为1的用户购买记录,并以购买日期降序排序时,可以使用以下SQL语句:

SELECT * FROM buy_log WHERE goods_id=1 ORDER BY buy_date DESC;

MySQL在优化器中会优先选择使用(userid,goods_id,buy_date)这个联合索引进行查询,因为索引中已经按照购买日期进行排序,无需再进行一次额外的排序操作。而如果我们只需要按照用户ID进行查询,那么就可以直接使用单列索引(userid)进行查询,以提高查询效率。

🎉 联合索引的优缺点

虽然联合索引可以提高查询效率,但也存在一些缺点。下面我们来详细介绍一下。

优点:

  1. 提高查询效率:联合索引能够提高查询效率,尤其是在同时筛选多个字段的时候。
  2. 减少IO次数:联合索引可以让MySQL更快地定位到需要查找的记录,从而减少IO次数。
  3. 覆盖索引:如果联合索引包含了所有需要查询的字段,那么只需要使用索引就可以得到查询结果,无需读取数据表,这就是覆盖索引(Covering Index)。

缺点:

  1. 索引长度:联合索引的长度会随着所包含的字段数量增加而增加,如果字段数量过多,那么索引长度就会变得很大。
  2. 索引更新:对于包含多个字段的联合索引,如果其中任何一个字段发生了更新,那么整个索引都需要更新,这就会导致索引更新操作比较耗时。
  3. 查询条件顺序:使用联合索引时,查询条件的顺序非常重要。如果条件的顺序不对,那么MySQL就无法使用索引,需要做全表扫描。

🎉 如何优化联合索引?

虽然联合索引可以提高查询效率,但在实际使用中,可能会出现无法使用索引或者索引效率不高等问题。这里我们列举几种常见的情况,并介绍如何优化联合索引。

📝 1. 索引长度过长导致索引失效

我们在设计联合索引时需要注意,不宜将太多的字段包含在一个联合索引中,否则会导致索引长度过长,从而导致索引失效。

比如,在之前的购买记录表(buy_log)中,如果需要同时对用户ID(userid)、商品ID(goods_id)、购买日期(buy_date)、商品名称(goods_name)和购买数量(buy_num)进行索引,那么就需要将这五个字段全部包含在一个联合索引中,代码如下:

ALTER TABLE buy_log ADD KEY (userid,goods_id,buy_date,goods_name,buy_num);

但是,由于联合索引的长度过长,MySQL可能会认为它并不是一个好的索引,从而决定不使用它。这个时候,我们可以通过拆分联合索引来解决这个问题。比如,我们可以将商品名称(goods_name)和购买数量(buy_num)这两个不经常用到的字段单独放在一个索引中,代码如下:

ALTER TABLE buy_log ADD KEY (userid,goods_id,buy_date);
ALTER TABLE buy_log ADD KEY (goods_name,buy_num);

这样就可以避免因为索引长度过长而导致索引失效的问题。

📝 2. 查询条件顺序不正确导致索引失效

使用联合索引时,查询条件的顺序非常重要。如果条件的顺序不对,那么MySQL就无法使用索引,需要做全表扫描。比如,在购买记录表(buy_log)中,如果需要查询商品ID为1的用户购买日期在2021年3月1日之后的购买记录,那么查询条件的顺序应该为(goods_id,buy_date,userid),代码如下:

SELECT * FROM buy_log WHERE goods_id=1 AND buy_date>'2021-03-01' AND userid=123;

如果按照(userid,goods_id,buy_date)的顺序来查询,那么MySQL就无法使用联合索引,需要做全表扫描。

📝 3. 索引更新导致性能下降

对于包含多个字段的联合索引,如果其中任何一个字段发生了更新,那么整个索引都需要更新,这就会导致索引更新操作比较耗时。因此,我们在设计联合索引时需要注意,尽量避免将经常更新的字段包含在联合索引中。

比如,在购买记录表(buy_log)中,如果经常需要更新购买日期(buy_date)这个字段,那么我们就应该将其单独放在一个索引中,代码如下:

ALTER TABLE buy_log ADD KEY (userid,goods_id);
ALTER TABLE buy_log ADD KEY (buy_date);

这样就可以避免因为索引更新而导致性能下降的问题。


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5月前
|
存储 关系型数据库 MySQL
【高频】什么是索引的下推和覆盖
【高频】什么是索引的下推和覆盖
207 2
【SQL开发实战技巧】系列(七):从有重复数据前提下如何比较出两个表中的差异数据及对应条数聊起
本篇文章讲解的主要内容是:***如果有重复数据如何检查出两个表中的差异数据及对应条数、表连接做聚合容易出现重复计算的错误、多表查询空值处理问题、NOT IN的子查询范围不能是空值,否则查询结果为空。***
【SQL开发实战技巧】系列(七):从有重复数据前提下如何比较出两个表中的差异数据及对应条数聊起
|
缓存 关系型数据库 MySQL
索引三表优化案例|学习笔记
快速学习索引三表优化案例
索引三表优化案例|学习笔记
|
SQL 存储 缓存
索引不是越多越好,理解索引结构原理,才有助于我们建立合适的索引!
MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等。为了避免混乱,本文将只关注于BTree索引,因为这是平常使用MySQL时主要打交道的索引。
653 0
|
数据库 索引
存在逻辑删除的表字段上建立唯一索引的巧办法 (逻辑删除与唯一索引)
设计数据库唯一索引时,经常会碰到唯一删除的键值,导致很难处理,这里就简单介绍一种巧办法,帮你快速解决该问题
1975 0
存在逻辑删除的表字段上建立唯一索引的巧办法 (逻辑删除与唯一索引)
|
SQL 缓存 监控
列表查询的通用优化方案
> 列表查询是服务端开发中非常高频的诉求,接口的性能往往会跟用户体验强关联。本文通过一个具体的例子,来总结服务端写查询接口时的通用优化方案。 ## 一个例子 ### 功能诉求 给出一个具体的例子,背景是根据内容ID来查询内容信息(如下),目标是通过编码优化使得这个查询效率变快,减少上游(客户端App或外部服务)的等待时间。 ```java public interfa
1343 2
列表查询的通用优化方案
数据蒋堂 | JOIN延伸 - 维度查询语法
有了维度定义后,我们就可以来梳理前面讲过的简化JOIN语法了。 先定义字段维度: 维度字段的维度为其本身; 外键字段的维度为相应外键表中关联字段的维度; 测度字段没有维度。 这是个递归定义。
2250 0