MySQL深分页进行性能优化的常见方法

2025-07-14 18:50

本文主要是介绍MySQL深分页进行性能优化的常见方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《MySQL深分页进行性能优化的常见方法》在Web应用中,分页查询是数据库操作中的常见需求,然而,在面对大型数据集时,深分页(deeppagination)却成为了性能优化的一个挑战,在本文中,我们将...

引言:深分页,真的只是“翻页慢”那么简单吗?

在面试中,你是否遇到过这样的问题?

“你了解 mysql 深分页的性能问题吗?如何优化?”

又或者在真实项目中,当你翻到第 1000 页的数据时,接口突然变得异常缓慢,甚至超时崩溃。你打开慢 SQL 日志,看到那条熟悉的 LIMIT 100000, 20,心里默默叹了口气:“啊,又是深分页惹的祸。&rdChina编程quo;

作为一名有 8 年开发经验的 Java 工程师,我在多个后台系统和数据中心项目中都遇到过深分页带来的性能瓶颈。起初我们只是加索引、调 SQL,但最终编程发现:分页的本质,其实是数据访问策略的设计问题

这篇文章将带你深入理解:

  • 为什么深分页会拖垮数据库
  • 如何结合实际业务场景进行优化?
  • 有哪些可落地的代码实践?
  • 如何用一个高性能的游标分页方案替代传统分页?

一、背景介绍

在日常业务开发中,分页查询是非常常见的需求。例如在管理后台系统中,展示订单列表、用户列表、日志记录等都需要分页加载。

通常我们会使用类似下面的 SQL:

SELECT * FROM orders ORDER BY create_time DESC LIMIT 100000, 20;

上述查询语句的含义是:跳过 100000 条记录,取第 100001 到 100020 条数据。这种分页方式我们称为“深分页”

二、深分页的性能问题

MySQL 在执行 LIMIT offset, size 的时候,会先扫描 offset + size 条记录,然后抛弃前 offset 条,只返回 size 条。

也就是说,LIMIT 100000, 20 实际上扫描了 100020 行,仅返回 20 行。若表数据量和 offset 很大,性能将急剧下降,甚至拖垮数据库。

三、业务场景分析

假设你在做一个订单系统,运营人员需要查看历史订单数据,而这些订单数据量非常庞大(千万级),他们经常会翻到第 1000 页查看订单(每页 20 条)。

你发现系统在翻页到后面时接口响应非常慢,排查后定位到是 MySQL 查询耗时严重。

这时候我们就需要 优化深分页的查询方式

四、优化思路

方法一:使用覆盖索引

SELECT id FROM orders ORDER BY id LIMIT 100000, 20;
SELECT * FROM orders WHERE id IN (...);

通过先查主键,再回表查详细数据,减少回表成本。

方法二:记录上一次的游标(推荐)

使用**“基于游标的分页”**,也叫作“Keyset Pagination”或“Seek 方法”。

思路是:不要使用 LIMIT offset,而是通过上一次查询的最后一条数据的主键或排序字段作为游标,下次查询直接从该游标之后开始。

SELECT * FROM orders WHERE id > ? ORDER BY id ASC LIMIT 20;

优势:

  • 避免跳过大量数据,性能优越
  • 更适合实时数据流或按时间排序的场景

五、实战代码:Java 工具类实现游标分页

我们封装一个通用的分页工具类,支持游标分页,适用于 Spring + MyBatis/MyBatis-Plus 项目。

1. 分页请求参数类

public class CursorPageRequest {
    /**
     android* 游标字段,如 id、时间戳等
     */
    private Long cursor;

    /**
     * 每页数量
     */
    private int pageSize = 20;

    public CursorPageRequest() {}

    public CursorPageRequest(Long cursor, int pageSize) {
        this.cursor = cursor;
        this.pageSize = pageSize;
    }

    public Long getCursor() {
        return cursor;
    }

    public void setCursor(Long cursor) {
        this.cursor = cursor;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }
}

2. 通用分页返回类

import jChina编程ava.util.List;

public class CursorPageResponse<T> {
    private List<T> data;
    private Long nextCursor;
    private boolean hasMore;

    public CursorPageResponse(List<T> data, Long nextCursor, boolean hasMore) {
        this.data = data;
        this.nextCursor = nextCursor;
        this.hasMore = hasMore;
    }

    public List<T> getData() {
        return data;
    }

    public Long getNextCursor() {
        return nextCursor;
    }

    public boolean isHasMore() {
        return hasMore;
    }
}

3. MyBatis 示例 Mapper 接口(以订单为例)

@Mapper
public interface OrderMapper {

    /**
     * 查询游标分页数据
     * @param cursor 上次最后一条记录的 ID
     * @param pageSize 每页数量
     */
    @Select("SELECT * FROM orders " +
            "WHERE (:cursor IS NULL OR id > #{cursor}) " +
            "ORDER BY id ASC LIMIT #{pageSize}")
    List<OrderDO> listByCursor(@Param("cursor") Long cursor, @Param("pageSize") Integer pageSize);
}

4. Service 层封装分页逻辑

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    public CursorPageResponse<OrderDO> listOrdersByCursor(CursorPageRequest request) {
        List<OrderDO> list = orderMapper.listByCursor(request.getCursor(), request.getPageSize());

        Long nextCursor = null;
        boolean hasMore = false;

        if (!list.isEmpty()) {
            // 获取最后一条记录的 ID 作为下次游标
            nextCursor = list.get(list.size() - 1).getId();
            hasMore = list.size() == request.getPageSize();
        }

        return new CursorPageResponse<>(list, nextCursor, hasMore);
    }
}

六、接口调用示例

前端每次请求:

GET /orders?cursor=10021&pageSize=20

返回:

{
  "data": [...],
  "nextCursor": 10041,
  "hasMore": true
pEDfaVpBoM}

前端将 nextCursor 作为下次请求的 cursor 参数实现“加载更多”效果。

七、总结

分页方式优点缺点
LIMIT offset, size简单通用深分页性能差
游标分页(Keyset)性能优秀,避免跳过大量数据不支持跳页,只支持顺序加载

建议:在数据量大、分页页码深的业务场景中,优先使用 游标分页,尤其是管理后台、数据分析系统、接口服务等。

八、建议与扩展

  • 游标字段最好是唯一递增的,比如主键 ID、时间戳等
  • 多字段组合游标可以支持更复杂的排序场景
  • 可以将分页封装为 AOP 或 BaseService 通用组件

以上就是MySQL深分页进行性能优化的常见方法的详细内容,更多关于MySQL深分页性能优化的资料请关注编程China编程(www.chinasem.cn)其它相关文章!

这篇关于MySQL深分页进行性能优化的常见方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155379

相关文章

MyBatis-Plus通用中等、大量数据分批查询和处理方法

《MyBatis-Plus通用中等、大量数据分批查询和处理方法》文章介绍MyBatis-Plus分页查询处理,通过函数式接口与Lambda表达式实现通用逻辑,方法抽象但功能强大,建议扩展分批处理及流式... 目录函数式接口获取分页数据接口数据处理接口通用逻辑工具类使用方法简单查询自定义查询方法总结函数式接口

MySql基本查询之表的增删查改+聚合函数案例详解

《MySql基本查询之表的增删查改+聚合函数案例详解》本文详解SQL的CURD操作INSERT用于数据插入(单行/多行及冲突处理),SELECT实现数据检索(列选择、条件过滤、排序分页),UPDATE... 目录一、Create1.1 单行数据 + 全列插入1.2 多行数据 + 指定列插入1.3 插入否则更

MySQL 迁移至 Doris 最佳实践方案(最新整理)

《MySQL迁移至Doris最佳实践方案(最新整理)》本文将深入剖析三种经过实践验证的MySQL迁移至Doris的最佳方案,涵盖全量迁移、增量同步、混合迁移以及基于CDC(ChangeData... 目录一、China编程JDBC Catalog 联邦查询方案(适合跨库实时查询)1. 方案概述2. 环境要求3.

JAVA中安装多个JDK的方法

《JAVA中安装多个JDK的方法》文章介绍了在Windows系统上安装多个JDK版本的方法,包括下载、安装路径修改、环境变量配置(JAVA_HOME和Path),并说明如何通过调整JAVA_HOME在... 首先去oracle官网下载好两个版本不同的jdk(需要登录Oracle账号,没有可以免费注册)下载完

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

SQL server数据库如何下载和安装

《SQLserver数据库如何下载和安装》本文指导如何下载安装SQLServer2022评估版及SSMS工具,涵盖安装配置、连接字符串设置、C#连接数据库方法和安全注意事项,如混合验证、参数化查... 目录第一步:打开官网下载对应文件第二步:程序安装配置第三部:安装工具SQL Server Manageme

C#连接SQL server数据库命令的基本步骤

《C#连接SQLserver数据库命令的基本步骤》文章讲解了连接SQLServer数据库的步骤,包括引入命名空间、构建连接字符串、使用SqlConnection和SqlCommand执行SQL操作,... 目录建议配合使用:如何下载和安装SQL server数据库-CSDN博客1. 引入必要的命名空间2.

全面掌握 SQL 中的 DATEDIFF函数及用法最佳实践

《全面掌握SQL中的DATEDIFF函数及用法最佳实践》本文解析DATEDIFF在不同数据库中的差异,强调其边界计算原理,探讨应用场景及陷阱,推荐根据需求选择TIMESTAMPDIFF或inte... 目录1. 核心概念:DATEDIFF 究竟在计算什么?2. 主流数据库中的 DATEDIFF 实现2.1

SpringBoot结合Docker进行容器化处理指南

《SpringBoot结合Docker进行容器化处理指南》在当今快速发展的软件工程领域,SpringBoot和Docker已经成为现代Java开发者的必备工具,本文将深入讲解如何将一个SpringBo... 目录前言一、为什么选择 Spring Bootjavascript + docker1. 快速部署与

MySQL 多列 IN 查询之语法、性能与实战技巧(最新整理)

《MySQL多列IN查询之语法、性能与实战技巧(最新整理)》本文详解MySQL多列IN查询,对比传统OR写法,强调其简洁高效,适合批量匹配复合键,通过联合索引、分批次优化提升性能,兼容多种数据库... 目录一、基础语法:多列 IN 的两种写法1. 直接值列表2. 子查询二、对比传统 OR 的写法三、性能分析