MySQL分页查询优化的实践指南

 更新时间:2025年12月25日 08:34:52   作者:·云扬·  
在日常业务开发中,分页查询是高频操作,比如列表页数据展示、历史记录查询等,但当数据量达到万级以上时,普通的limit分页往往会出现性能瓶颈,本文基于实际测试场景,详细分析MySQL分页查询优化的实践指南,需要的朋友可以参考下

引言

在日常业务开发中,分页查询是高频操作,比如列表页数据展示、历史记录查询等。但当数据量达到万级以上时,普通的limit分页往往会出现性能瓶颈。本文基于实际测试场景,详细分析MySQL分页查询的执行原理,并针对不同排序场景提供优化方案,附完整测试代码与执行计划对比。

一、测试环境搭建:模拟万级数据量

为了更真实地复现分页查询问题,我们先创建测试表并插入10万条测试数据,确保测试环境的一致性。

1.1 创建测试表

use martin;  -- 切换到目标数据库
drop table if exists t1;  -- 若表已存在则删除

CREATE TABLE `t1` (            
  `id` int NOT NULL auto_increment,  -- 自增主键
  `a` int DEFAULT NULL,              -- 普通字段,用于非主键排序测试
  `b` int DEFAULT NULL,              -- 普通字段
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
  PRIMARY KEY (`id`),  -- 主键索引
  KEY `idx_a` (`a`),   -- 为字段a创建普通索引,用于非主键排序优化
  KEY `idx_b` (`b`)    -- 为字段b创建普通索引(备用)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

1.2 批量插入测试数据

通过存储过程批量插入10万条数据,避免手动插入的繁琐:

drop procedure if exists insert_t1;  -- 若存储过程已存在则删除
delimiter ;;  -- 修改语句结束符,避免与存储过程内的分号冲突
create procedure insert_t1()       
begin
  declare i int;                  
  set i=1;                        
  while(i<=100000)do  -- 插入10万条数据
    insert into t1(a,b) values(i, i);  -- a、b字段值与自增id一致
    set i=i+1;                  
  end while;
end;;
delimiter ;  -- 恢复语句结束符为分号
call insert_t1();  -- 调用存储过程插入数据

二、基础分页查询:问题与执行计划分析

最常见的分页查询方式是使用limit offset, size,但当offset(偏移量)较大时,性能会显著下降。我们以“查询第10001-10010条数据”为例,分析其执行逻辑。

2.1 普通limit分页SQL

-- 查询a、b字段,跳过前10000条,取10条
select a,b from t1 limit 10000,10;

2.2 执行计划分析

通过explain查看SQL执行计划,关键信息如下(对应测试截图结果):

  • type:可能为ALL(全表扫描)或range(范围扫描),取决于是否使用索引;
  • key:若未命中索引,key字段为空,意味着需要扫描全表数据;
  • rows:扫描行数接近10010行(需跳过前10000行,再取10行),数据量越大,扫描行数越多,性能越差。

核心问题limit 10000,10会先扫描前10010条数据,再丢弃前10000条,仅返回最后10条,大量数据的“无效扫描”导致性能损耗。

三、优化方案一:基于自增连续主键的分页查询

若分页查询基于自增且连续的主键排序(如按id升序),可通过“主键范围查询”替代limit offset,彻底避免无效数据扫描。

3.1 优化后的SQL

-- 直接查询id在10001-10010之间的数据,无需跳过前10000条
select a,b from t1 where id > 10000 and id <= 10010;

3.2 执行计划对比

再次使用explain分析优化后的SQL,执行计划发生显著变化:

  • type:变为range(范围扫描),仅扫描主键索引中id在10001-10010之间的记录;
  • key:命中主键索引(PRIMARY),无需扫描全表;
  • rows:扫描行数仅为10行,与需要返回的数据量完全一致,性能大幅提升。

3.3 关键注意事项

此方案的前提是主键必须连续。若主键不连续(如删除过数据),会导致查询结果与普通limit分页不一致,示例如下:

先删除一条数据,破坏主键连续性:

delete from t1 where id=10;  -- 删除id=10的记录

对比两种查询结果:

  • 普通limitselect a,b from t1 limit 10000,10会跳过前10000条(包含被删除的id=10,实际扫描10001条有效数据),返回第10001-10010条有效数据;
  • 主键范围查询:select a,b from t1 where id >10000 and id <=10010会跳过id=10的空缺,直接返回id=10001-10010的10条数据,与预期结果不一致。

适用场景:主键自增且无删除操作的表(如日志表、流水表)。

四、优化方案二:基于非主键字段排序的分页查询

若分页查询需要按非主键字段排序(如按a字段升序),直接使用order by + limit会触发filesort(文件排序),性能极差。我们通过“子查询查主键 + 关联查详情”的方式优化。

4.1 普通非主键排序分页的问题

以“按a字段排序,查询第99001-99002条数据”为例,普通SQL如下:

select * from t1 order by a limit 99000,2;
  • 执行计划问题order by a会触发filesort(即使a字段有索引idx_a,若查询字段包含非索引字段,仍需回表,可能导致filesort);
  • 性能损耗:需扫描大量数据并排序,数据量越大,排序耗时越长。

4.2 优化后的SQL

核心思路:先通过子查询仅查询“排序后的主键id”(利用索引避免filesort),再通过主键关联查询完整数据:

-- 子查询:按a排序,取第99001-99002条的id(仅扫描索引,无filesort)
-- 主查询:通过id关联表t1,查询完整数据(主键关联性能极高)
select f.* from t1 f 
inner join (select id from t1 order by a limit 99000,2) g 
on f.id = g.id;

4.3 执行计划优化点

  • 子查询select id from t1 order by a limit 99000,2命中索引idx_atypeindex,无filesort,仅扫描99002条索引记录(远少于全表扫描);
  • 主查询:通过主键id关联,typeeq_ref(主键等值匹配,性能最优),rows仅为2行,无额外性能损耗。

适用场景:所有需要按非主键字段排序的分页查询,尤其适合数据量超过10万级的表。

五、总结:不同场景的分页查询选型

分页场景推荐方案优点注意事项
主键自增且连续、按id排序where id > offset and id <= offset+size无无效扫描,性能最优主键必须连续,无删除操作
非主键字段排序子查询查id + 主键关联查详情避免filesort,减少扫描行数需为排序字段创建索引
主键不连续、按id排序保留普通limit,或结合覆盖索引结果准确,兼容性强可通过“覆盖索引”减少全表扫描范围

通过以上优化方案,可有效解决MySQL分页查询在大数据量下的性能问题,实际项目中需根据业务场景(排序字段、主键连续性)选择合适的方案,并结合索引设计进一步提升性能。

以上就是MySQL分页查询优化的实践指南的详细内容,更多关于MySQL分页查询优化的资料请关注脚本之家其它相关文章!

相关文章

  • MySQL错误提示:sql_mode=only_full_group_by完美解决方案

    MySQL错误提示:sql_mode=only_full_group_by完美解决方案

    有时候遇到数据库重复数据,需要将数据进行分组,并取出其中一条来展示,这时就需要用到group by语句,下面这篇文章主要给大家介绍了关于MySQL错误提示:sql_mode=only_full_group_by的完美解决方案,需要的朋友可以参考下
    2022-10-10
  • SQL实现LeetCode(197.上升温度)

    SQL实现LeetCode(197.上升温度)

    这篇文章主要介绍了SQL实现LeetCode(197.上升温度),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • 如何在SQL Server中实现 Limit m,n 的功能

    如何在SQL Server中实现 Limit m,n 的功能

    本篇文章是对在SQL Server中实现 Limit m,n功能的方法进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • mysql中varchar类型的日期进行比较、排序等操作的实现

    mysql中varchar类型的日期进行比较、排序等操作的实现

    在mysql使用过程中,日期一般都是以datetime、timestamp等格式进行存储的,但有时会因为特殊的需求或历史原因,日期的存储格式是varchar,那么应该怎么进行比较和排序等问题,本文就来介绍一下
    2021-11-11
  • Ubuntu移除mysql后重新安装的方法

    Ubuntu移除mysql后重新安装的方法

    这篇文章主要介绍了Ubuntu移除mysql后重新安装的方法,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-04-04
  • SQL实战演练之网上商城数据库商品类别数据操作

    SQL实战演练之网上商城数据库商品类别数据操作

    一直认为,扎实的SQL功底是一名数据分析师的安身立命之本,甚至可以称得上是所有数据从业者的基本功。当然,这里的SQL绝不单单是写几条查询语句那么简单,接下来请跟着小编通过案例项目演练一遍商品类别的数据操作吧
    2021-10-10
  • mysql的json处理实现

    mysql的json处理实现

    本文主要介绍了mysql的json处理实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-08-08
  • 计算机二级考试MySQL常考点 8种MySQL数据库设计优化方法

    计算机二级考试MySQL常考点 8种MySQL数据库设计优化方法

    这篇文章主要为大家详细介绍了计算机二级考试MySQL常考点,详细介绍8种MySQL数据库设计优化方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • MySQL修改root密码的3种实用方法

    MySQL修改root密码的3种实用方法

    最近在看项目,搭建本地环境时候,忘记mysql的密码,怎么修改密码,网上找了半天,终于配合着几个帖子搞定了,下面这篇文章主要给大家介绍了关于MySQL修改root密码的3种实用方法,需要的朋友可以参考下
    2023-11-11
  • mysql 表空间及索引的查看方法

    mysql 表空间及索引的查看方法

    mysql 表空间及索引的查看方法,需要的朋友可以参考下。
    2011-07-07

最新评论