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

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符

Python版本与package版本兼容性检查方法总结

《Python版本与package版本兼容性检查方法总结》:本文主要介绍Python版本与package版本兼容性检查方法的相关资料,文中提供四种检查方法,分别是pip查询、conda管理、PyP... 目录引言为什么会出现兼容性问题方法一:用 pip 官方命令查询可用版本方法二:conda 管理包环境方法

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

深入理解Mysql OnlineDDL的算法

《深入理解MysqlOnlineDDL的算法》本文主要介绍了讲解MysqlOnlineDDL的算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小... 目录一、Online DDL 是什么?二、Online DDL 的三种主要算法2.1COPY(复制法)

mysql8.0.43使用InnoDB Cluster配置主从复制

《mysql8.0.43使用InnoDBCluster配置主从复制》本文主要介绍了mysql8.0.43使用InnoDBCluster配置主从复制,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录1、配置Hosts解析(所有服务器都要执行)2、安装mysql shell(所有服务器都要执行)3、

JavaScript对象转数组的三种方法实现

《JavaScript对象转数组的三种方法实现》本文介绍了在JavaScript中将对象转换为数组的三种实用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录方法1:使用Object.keys()和Array.map()方法2:使用Object.entr

k8s中实现mysql主备过程详解

《k8s中实现mysql主备过程详解》文章讲解了在K8s中使用StatefulSet部署MySQL主备架构,包含NFS安装、storageClass配置、MySQL部署及同步检查步骤,确保主备数据一致... 目录一、k8s中实现mysql主备1.1 环境信息1.2 部署nfs-provisioner1.2.