MySQL 分页查询优化——延迟关联优化

本文探讨了MySQL分页查询的优化策略,重点介绍了如何利用覆盖索引和延迟关联技术减少回表次数,显著提升查询效率,适用于大量数据的分页场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MySQL 分页查询优化——延迟关联优化

目录

1.   InnoDB表的索引的几个概念

2.   覆盖索引和回表

3.   分页查询

4.   延迟关联优化

写在前面

下面的介绍均是在选用MySQL数据库和Innodb引擎的基础开展。我们先来学习索引的几个概念,帮助我们理解延迟关联优化的加快分页查询速度的原因。

一、Innodb表的索引的几个概念

InnoDB表是基于聚簇索引建立的。

索引一般分为主键索引和普通索引(辅助索引),聚簇索引并不是主键索引这样的单独的索引类型,而是一种数据存储方式。通俗的来说,单独的索引是存储了索引信息的B+Tree,而聚簇索引是在同一个结构中保存了B+Tree和数据行,即通过主键索引B+Tree的结构建立数据文件(网上的说法是索引和数据存储在同一个文件中),因此聚簇索引是一种数据存储方式。

如果对于索引的概念不是很熟悉,建议去查阅相关资料学习,索引是一个很庞大的知识结构。

 

Innodb表以主键索引建立后缀名为.MYD表存储文件,普通索引亦以B+Tree实现,并保存在后缀名为.MYI的文件中,具体结构图如下所示:

 

 

 


 

在查询时选用主键索引和普通索引有什么不一样呢?

以下面语句为例:

·       select * from table where id = 5;id为主键索引) 检索过程如上图绿色箭头所示,直接在主索引树上根据主键检索

·       select * from table where name = "Gates"; name为普通索引)检索过程如上图红色箭头所示,先根据普通索引检索普通索引树得到id5,然后再拿得到的id到主索引树检索得到结果。在这个过程中,回到主键索引树搜索的过程,我们称为回表。

 

从这里可以看出,基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。

解释完普通索引和主键索引的检索过程,让我们来看看什么是覆盖索引。

二、覆盖索引和回表。

什么是覆盖索引

查询的列被所建的辅助索引所覆盖,无需回表。用大白话解释就是,要查的数据直接可以从索引树上就能取得,无需回表查找。

注意:不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引的列,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree索引做覆盖索引

结合上图的例子:

·       select id from table where name = "Gates"; 即为一个覆盖索引。

 

三、分页查询使用场景

需求:查询最近 7 天的订单,并做分页。订单表数据量:3000W

未经优化的SQL

select * from t_trade_order

where create_time between '2019-10-17' and '2019-10-25'

limit 1000000, 10;

 

 

 

 

 

 

根据explain输出的结果可知,这是一条慢查询,在业务环境中不允许出现这样的慢查询。

 

我们都知道在做分页时会用到Limit关键字去筛选所需数据,limit接受1个或者2个参数,接受两个参数时第一个参数表示偏移量,即从哪一行开始取数据,第二个参数表示要取的行数。 如果只有一个参数,相当于偏移量为0。当偏移量很大时,如limit 100000,10 取第100001-100010条记录,mysql会取出100010条记录然后将前100000条记录丢弃,这无疑是一种巨大的性能浪费。

 

终于转入正题了,那我们应该怎样优化呢?

 

四、延迟关联优化

《高性能MySQL》书中其实也讨论这个情况:

 

 

 

延迟关联优化:通过使用覆盖索引查询返回需要的主键,再根据主键关联原表获得需要的数据。

 

select * from t_trade_order t

inner join (

     select id from t_trade_order

    where create_time between '2019-10-17' and '2019-10-25'

     limit 1000000, 10

) e

on t.id = e.id;

 

 

 

根据explain分析,查询时间仅为0.31,比普通的分页查询快了一个数量级。

 

想必大家会想知道,为什么延迟关联对比普通分页查询可以起到这样的优化效果呢?

这就涉及到上面所讲的覆盖索引和回表这两个重要概念了!

 

我们来看看这两条语句的执行流程就很清楚了。

优化前:

select * from t_trade_order

where create_time between '2019-10-17' and '2019-10-25'

limit 1000000, 10;

create_time建表时被设置为普通索引)

 

1.create_time索引树上找到create_time=‘2019-10-17’的记录,取得其id

2.再到主索引树查到对于id的记录

3.如数量小于10,更新时间,循环步骤12

4. 。。。

5.create_time索引树取下一个值create_time='2019-10-25',不满足条件,循环结束。

6.查询结果放弃前1000000行,返回10

 

显然,普通的分页查询是逐一通过普通索引获得id然后回表查询,每次回表进行一次IO,造成相当大的性能浪费。

 

优化后

select * from t_trade_order t

inner join (

    select id from t_trade_order

   where create_time between '2019-10-17' and '2019-10-25'

    limit 1000000, 10

) e

on t.id = e.id;

 

1.使用覆盖索引,select id from t_trade_order where create_time between '2019-10-17' and '2019-10-25' limit 1000000, 10,查询结果放弃前1000000行,返回10行,查询出符合查询范围的id

2、回表关联,根据获得的id关联主索引表,批量匹配得到结果。(只需回到主索引表一次)

 

由此可知,通过使用覆盖索引查询返回需要的主键,再根据这些主键关联原表获得需要的行,这可以减少MySQL回表的次数,也避免了MySQL直接在原表上扫描那些需要丢弃的行数(实则在普通索引树上扫描,速度快很多)。

### 如何在 MySQL 中实现分页查询的最佳实践 #### 使用 `LIMIT` 和 `OFFSET` 实现基本分页查询 在 Java 应用程序中操作 MySQL 数据库时,可以利用 SQL 的 `LIMIT` 和 `OFFSET` 子句来实现分页查询。这种技术通过指定返回记录的数量以及跳过的记录数量来控制每一页的数据范围[^1]。 ```sql SELECT * FROM table_name LIMIT page_size OFFSET (page_number - 1) * page_size; ``` 上述代码片段展示了如何基于当前页面编号 (`page_number`) 和每页大小 (`page_size`) 来构建分页查询语句。这种方法简单易懂,在数据量较小时表现良好。 --- #### 高效替代方案:基于主键的分页查询 当面对大规模数据集时,传统的 `LIMIT` 和 `OFFSET` 方法可能会导致性能瓶颈,因为随着偏移量增大,数据库需要扫描更多的行才能定位目标数据[^2]。为了缓解这一问题,推荐采用基于主键或其他唯一索引字段的方法进行分页查询: ```sql SELECT * FROM table_name WHERE id > last_id ORDER BY id ASC LIMIT page_size; ``` 在此模式下,每次查询都以前一次获取的最大 ID 值作为起点继续检索新一批数据。此策略显著减少了不必要的磁盘 I/O 操作并提升了整体效率[^3]。 --- #### 大规模场景下的进一步优化措施 对于包含数百万甚至上亿条记录的大表而言,即使采用了基于主键的方式也可能遇到挑战。此时可考虑引入覆盖索引、延迟关联等高级技巧来增强性能[^4]。例如: - **创建复合索引**:确保用于排序和过滤条件的关键列已被适当索引化; - **减少回表次数**:尽可能只选取所需字段而非整个行内容; - **预计算汇总视图**:如果某些统计信息频繁访问,则可通过维护物化视图降低实时查询压力; 以下是结合这些原则的一个实际例子——针对某月车辆行驶轨迹表执行高效分页读取: ```sql -- 此处假设存在按时间戳升序排列且带有索引支持的时间列'timestamp' SELECT vehicle_id, timestamp, location FROM trip_records WHERE timestamp >= :lastTimestamp AND month = 'target_month' ORDER BY timestamp ASC LIMIT 100; ``` 这里我们不仅限定了日期区间还加入了上次结束位置标记(`:lastTimestamp`)从而实现了连续滚动加载效果同时兼顾速度与资源消耗平衡。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值