記一次mysql線上分頁排序亂序問題解決
1. 觸發(fā)版本
MySQL 8.0
2. 問題描述
優(yōu)化列表分頁查詢接口時,將默認(rèn)的無排序條件查詢修改為按照時間(create_at)條件排序,由于 order by 與 limit 混用時未加唯一限定排序條件,導(dǎo)致分頁結(jié)果中出現(xiàn)了跨頁重復(fù)的數(shù)據(jù)。
3. 問題復(fù)現(xiàn)
3.1 復(fù)現(xiàn)數(shù)據(jù)腳本
-- 1. 創(chuàng)建測試表
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. 創(chuàng)建存儲過程
DELIMITER $$
DROP PROCEDURE IF EXISTS generate_pagination_test_data$$
CREATE PROCEDURE generate_pagination_test_data(
IN total_rows INT, -- 總記錄數(shù)
IN batch_size INT, -- 批次大?。ㄏ嗤瑫r間的記錄數(shù))
IN start_date VARCHAR(19), -- 開始時間
IN end_date VARCHAR(19) -- 結(jié)束時間
)
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);
-- 計算總批次數(shù)和批次時間間隔
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;
-- 設(shè)置初始批次時間
SET batch_time = start_time;
SET current_batch = 0;
-- 性能優(yōu)化設(shè)置
SET autocommit = 0;
SET unique_checks = 0;
SET foreign_key_checks = 0;
-- 開始生成數(shù)據(jù)
WHILE i < total_rows DO
-- 如果批次計數(shù)器為0,表示開始新批次
IF batch_count = 0 THEN
-- 為這個批次生成一個時間
SET batch_time = DATE_ADD(start_time,
INTERVAL current_batch * interval_seconds +
FLOOR(RAND() * 60) SECOND -- 加一些隨機(jī)偏移
);
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
);
-- 更新計數(shù)器
SET i = i + 1;
SET batch_count = batch_count + 1;
-- 如果達(dá)到批次大小,重置批次計數(shù)器
IF batch_count >= batch_size THEN
SET batch_count = 0;
END IF;
-- 每5000條提交一次
IF i % 5000 = 0 THEN
COMMIT;
END IF;
END WHILE;
-- 最終提交
COMMIT;
-- 恢復(fù)設(shè)置
SET autocommit = 1;
SET unique_checks = 1;
SET foreign_key_checks = 1;
END$$
DELIMITER ;
-- 3. 調(diào)用存儲過程生成數(shù)據(jù)
-- 生成1萬條數(shù)據(jù),每批次7條相同時間,時間從2026-01-01到2026-01-01
CALL generate_pagination_test_data(
10000, -- 總記錄數(shù)
7, -- 批次大?。ㄏ嗤瑫r間的記錄數(shù))
'2026-01-01 00:00:00',
'2026-01-01 23:59:59'
);
-- 4. 清理存儲過程
DROP PROCEDURE IF EXISTS generate_pagination_test_data;
3.2 復(fù)現(xiàn)條件
執(zhí)行分頁查詢 (第3頁, 每頁10條)
select * from test_pagination order by created_at limit 20, 10

執(zhí)行分頁查詢(第4頁, 每頁10條)
select * from test_pagination order by created_at limit 30, 10
?

結(jié)果中 ID 為 31 的數(shù)據(jù)再次出現(xiàn),存在跨頁數(shù)據(jù)重復(fù)問題。
4. 問題分析
4.1 問題溯源
與前端聯(lián)調(diào)時發(fā)現(xiàn)分頁數(shù)據(jù)重復(fù)、主鍵 ID 順序不穩(wěn)定,與未加排序條件前的結(jié)果不一致,定位到核心問題為ORDER BY + LIMIT的使用方式不正確所導(dǎo)致。
4.2 核心原理
MySQL 5.6+ 對 ORDER BY ... LIMIT n 場景做了優(yōu)化
- 若排序無法利用索引有序特性,會啟用優(yōu)先隊列(Priority Queue) 進(jìn)行排序優(yōu)化。不再對全量數(shù)據(jù)排序,而是維護(hù)一個大小為
limit n的堆結(jié)構(gòu),遍歷數(shù)據(jù)時僅將符合條件的數(shù)據(jù)插入堆中,遍歷完成后直接取堆內(nèi)數(shù)據(jù)返回。 - 當(dāng)排序字段(
created_at)存在重復(fù)值時,MySQL 無法保證相同時間的記錄選取順序固定,對相同時間的記錄隨機(jī)保留最終導(dǎo)致分頁結(jié)果亂序、重復(fù)。
4.3 歷史邏輯分析
迭代前未顯式指定ORDER BY條件時,MySQL 會按聚簇索引的物理存儲順序進(jìn)行隱式排序(看似無重復(fù)),實際開發(fā)過程中中不應(yīng)該依賴該特性,所有排序查詢都需要顯式指定ORDER BY條件。
4.4 文檔資料
來源:MySQL 8.0 官方文檔 - ORDER BY 優(yōu)化
核心內(nèi)容:
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的排序條件末尾補(bǔ)充具有唯一性的排序字段(主鍵 ID、唯一索引等),確保相同排序字段值的記錄排序順序固定,徹底解決分頁重復(fù)問題。
5.1 優(yōu)化后的式例
-- 優(yōu)化前 SELECT * FROM test_pagination ORDER BY created_at LIMIT 20, 10; -- 優(yōu)化后 SELECT * FROM test_pagination ORDER BY created_at, id LIMIT 20, 10;
5.2 優(yōu)化原則
使用排序字段 + 唯一特性字段組合排
- 使用
created_at字段排序滿足業(yè)務(wù)側(cè)按時間展示需要。 - 時間相同的記錄按照主鍵值進(jìn)行二次排序,避免分頁亂序。
6. 避坑指南
- 所有分頁查詢的
ORDER BY條件必須包含唯一字段(主鍵 / 唯一索引),避免分頁亂序; - 避免依賴 MySQL 隱式排序特性(無
ORDER BY時按照物理存儲順序排序),顯式指定排序條件;
到此這篇關(guān)于記一次mysql線上分頁排序亂序問題解決的文章就介紹到這了,更多相關(guān)mysql線上分頁排序亂序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
CentOS 7 安裝Percona Server+Mysql
這篇文章主要介紹了CentOS 7 安裝Percona Server+Mysql的相關(guān)資料,需要的朋友可以參考下2018-11-11
MySQL數(shù)據(jù)庫服務(wù)多實例部署完整指南
MySQL多實例部署指的是在同一臺服務(wù)器上同時運(yùn)行多個獨(dú)立的MySQL實例,每個實例有自己的配置、數(shù)據(jù)目錄和端口,這篇文章主要介紹了MySQL數(shù)據(jù)庫服務(wù)多實例部署的相關(guān)資料,需要的朋友可以參考下2026-04-04
CentOS7環(huán)境下MySQL8常用命令小結(jié)
在進(jìn)行MySQL的優(yōu)化之前必須要了解的就是MySQL的查詢過程,下面這篇文章主要給大家介紹了關(guān)于CentOS7環(huán)境下MySQL8常用命令的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06

