记一次mysql线上分页排序乱序问题解决

 更新时间:2026年03月05日 10:08:47   作者:姜文宪  
本文主要介绍了mysql线上分页排序乱序问题解决,由于在分页查询时未正确使用ORDERBY和LIMIT导致的跨页数据重复问题,下面就来详细的介绍一下解决方法,感兴趣的可以了解一下

1. 触发版本

MySQL 8.0

2. 问题描述

优化列表分页查询接口时,将默认的无排序条件查询修改为按照时间(create_at)条件排序,由于 order by 与 limit 混用时未加唯一限定排序条件,导致分页结果中出现了跨页重复的数据。

3. 问题复现

3.1 复现数据脚本

-- 1. 创建测试表
DROP TABLE IF EXISTS test_pagination;
CREATE TABLE test_pagination (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    status VARCHAR(20),
    created_at DATETIME,
    updated_at DATETIME
);

-- 2. 创建存储过程
DELIMITER $$

DROP PROCEDURE IF EXISTS generate_pagination_test_data$$
CREATE PROCEDURE generate_pagination_test_data(
    IN total_rows INT,          -- 总记录数
    IN batch_size INT,          -- 批次大小(相同时间的记录数)
    IN start_date VARCHAR(19),  -- 开始时间
    IN end_date VARCHAR(19)     -- 结束时间
)
BEGIN
    DECLARE i INT DEFAULT 0;
    DECLARE batch_count INT DEFAULT 0;
    DECLARE batch_time DATETIME;
    DECLARE start_time DATETIME;
    DECLARE end_time DATETIME;
    DECLARE total_seconds INT;
    DECLARE interval_seconds INT;
    DECLARE total_batches INT;
    DECLARE current_batch INT DEFAULT 0;

    -- 所有变量声明
    SET start_time = STR_TO_DATE(start_date, '%Y-%m-%d %H:%i:%s');
    SET end_time = STR_TO_DATE(end_date, '%Y-%m-%d %H:%i:%s');
    SET total_seconds = TIMESTAMPDIFF(SECOND, start_time, end_time);

    -- 计算总批次数和批次时间间隔
    SET total_batches = CEIL(total_rows / batch_size);
    SET interval_seconds = FLOOR(total_seconds / total_batches);
    IF interval_seconds < 1 THEN SET interval_seconds = 1; END IF;

    -- 设置初始批次时间
    SET batch_time = start_time;
    SET current_batch = 0;

    -- 性能优化设置
    SET autocommit = 0;
    SET unique_checks = 0;
    SET foreign_key_checks = 0;

    -- 开始生成数据
    WHILE i < total_rows DO
        -- 如果批次计数器为0,表示开始新批次
        IF batch_count = 0 THEN
            -- 为这个批次生成一个时间
            SET batch_time = DATE_ADD(start_time,
                INTERVAL current_batch * interval_seconds +
                FLOOR(RAND() * 60) SECOND  -- 加一些随机偏移
            );
            SET current_batch = current_batch + 1;
        END IF;

        -- 插入记录(同批次记录时间相同)
        INSERT INTO test_pagination (status, created_at, updated_at)
        VALUES (
            CASE
                WHEN RAND() < 0.6 THEN 'active'
                WHEN RAND() < 0.9 THEN 'pending'
                ELSE 'deleted'
            END,
            batch_time,
            batch_time
        );

        -- 更新计数器
        SET i = i + 1;
        SET batch_count = batch_count + 1;

        -- 如果达到批次大小,重置批次计数器
        IF batch_count >= batch_size THEN
            SET batch_count = 0;
        END IF;

        -- 每5000条提交一次
        IF i % 5000 = 0 THEN
            COMMIT;
        END IF;
    END WHILE;

    -- 最终提交
    COMMIT;

    -- 恢复设置
    SET autocommit = 1;
    SET unique_checks = 1;
    SET foreign_key_checks = 1;

END$$

DELIMITER ;

-- 3. 调用存储过程生成数据
-- 生成1万条数据,每批次7条相同时间,时间从2026-01-01到2026-01-01
CALL generate_pagination_test_data(
    10000,      -- 总记录数
    7,          -- 批次大小(相同时间的记录数)
    '2026-01-01 00:00:00',
    '2026-01-01 23:59:59'
);

-- 4. 清理存储过程
DROP PROCEDURE IF EXISTS generate_pagination_test_data;

3.2 复现条件

  1. 执行分页查询 (第3页, 每页10条)

    select * from test_pagination order by created_at limit 20, 10
    

  1. 执行分页查询(第4页, 每页10条)

    select * from test_pagination order by created_at limit 30, 10
    

结果中 ID 为 31 的数据再次出现,存在跨页数据重复问题。

4. 问题分析

4.1 问题溯源

与前端联调时发现分页数据重复、主键 ID 顺序不稳定,与未加排序条件前的结果不一致,定位到核心问题为ORDER BY + LIMIT的使用方式不正确所导致。

4.2 核心原理

MySQL 5.6+ 对 ORDER BY ... LIMIT n 场景做了优化

  • 若排序无法利用索引有序特性,会启用优先队列(Priority Queue) 进行排序优化。不再对全量数据排序,而是维护一个大小为limit n的堆结构,遍历数据时仅将符合条件的数据插入堆中,遍历完成后直接取堆内数据返回。
  • 当排序字段(created_at)存在重复值时,MySQL 无法保证相同时间的记录选取顺序固定,对相同时间的记录随机保留最终导致分页结果乱序、重复。

4.3 历史逻辑分析

迭代前未显式指定ORDER BY条件时,MySQL 会按聚簇索引的物理存储顺序进行隐式排序(看似无重复),实际开发过程中中不应该依赖该特性,所有排序查询都需要显式指定ORDER BY条件。

4.4 文档资料

来源:MySQL 8.0 官方文档 - ORDER BY 优化

核心内容:

If you combine LIMIT *row_count* with ORDER BY, MySQL stops sorting as soon as it has found the first row_count rows of the sorted result, rather than sorting the entire result. If ordering is done by using an index, this is very fast. If a filesort must be done, all rows that match the query without the LIMIT clause are selected, and most or all of them are sorted, before the first row_count are found. After the initial rows have been found, MySQL does not sort any remainder of the result set.

One manifestation of this behavior is that an ORDER BY query with and without LIMIT may return rows in different order, as described later in this section.

5. 解决方案

ORDER BY的排序条件末尾补充具有唯一性的排序字段(主键 ID、唯一索引等),确保相同排序字段值的记录排序顺序固定,彻底解决分页重复问题。

5.1 优化后的式例

-- 优化前
SELECT * FROM test_pagination ORDER BY created_at LIMIT 20, 10;

-- 优化后
SELECT * FROM test_pagination ORDER BY created_at, id LIMIT 20, 10;

5.2 优化原则

使用排序字段 + 唯一特性字段组合排

  • 使用created_at字段排序满足业务侧按时间展示需要。
  • 时间相同的记录按照主键值进行二次排序,避免分页乱序。

6. 避坑指南

  1. 所有分页查询的ORDER BY条件必须包含唯一字段(主键 / 唯一索引),避免分页乱序;
  2. 避免依赖 MySQL 隐式排序特性(无ORDER BY时按照物理存储顺序排序),显式指定排序条件;

到此这篇关于记一次mysql线上分页排序乱序问题解决的文章就介绍到这了,更多相关mysql线上分页排序乱序内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MySQL索引的各种类型

    MySQL索引的各种类型

    这篇文章主要介绍了MySQL索引的各种类型,帮助大家更好的理解和学习MySQL索引,感兴趣的朋友可以了解下
    2020-09-09
  • mysql存储过程事务管理简析

    mysql存储过程事务管理简析

    本文将提供了一个绝佳的机制来定义、封装和管理事务,需要的朋友可以参考下
    2012-11-11
  • mysql中如何查询多个表中的数据量

    mysql中如何查询多个表中的数据量

    这篇文章主要介绍了mysql中如何查询多个表中的数据量问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • MySQL更改默认字符集为utf-8的全过程

    MySQL更改默认字符集为utf-8的全过程

    这篇文章主要介绍了MySQL更改默认字符集为utf-8的全过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • 教你如何在windows与linux系统中设置MySQL数据库名、表名大小写敏感

    教你如何在windows与linux系统中设置MySQL数据库名、表名大小写敏感

    数据库和表名在 Windows 中是大小写不敏感的,而在大多数类型的 Unix/Linux 系统中是大小写敏感的。那么我们如何来处理这个问题呢,经过一番查询,发现lower_case_table_names这个参数可以实现大小写敏感,下面我们来详细说明
    2014-08-08
  • mysql获取字符串长度函数(CHAR_LENGTH)

    mysql获取字符串长度函数(CHAR_LENGTH)

    本文介绍一下关于mysql获取字符串长度的方法,希望此教程对各位同学会有所帮助哦。
    2013-11-11
  • window下mysql 8.0.15 winx64安装配置方法图文教程

    window下mysql 8.0.15 winx64安装配置方法图文教程

    这篇文章主要为大家详细介绍了window下mysql 8.0.15 winx64安装配置方法图文教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-03-03
  • MySQL 筛选条件放 ON后 vs 放 WHERE 后的区别解析

    MySQL 筛选条件放 ON后 vs 放 WHERE 后的区

    文章解释了在MySQL中,将筛选条件放在ON和WHERE中的区别,文章通过几个场景说明了ON和WHERE的区别,并总结了ON用于关联,WHERE用于过滤,以及在LEFT JOIN中ON用于保行,在INNER JOIN中用于提效,本文介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2025-12-12
  • mysql sql_mode数据验证检查方法

    mysql sql_mode数据验证检查方法

    sql_mode 会影响MySQL支持的sql语法以及执行的数据验证检查,通过设置sql_mode ,可以完成不同严格程度的数据校验,有效地保障数据准确性,这篇文章主要介绍了mysql sql_mode数据验证检查,需要的朋友可以参考下
    2023-08-08
  • MySQL慢查询SQL优化方式

    MySQL慢查询SQL优化方式

    这篇文章主要介绍了MySQL慢查询SQL优化方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04

最新评论