783 字
4 分钟
MySQL 和 Redis 偏移量分页在数据增删场景下的问题与解决方案

一、问题背景:偏移量分页的固有缺陷#

无论是 MySQL 的 LIMIT offset, size,还是 Redis 的 ZRANGE key start stop,它们都基于结果集中的位置(偏移量)进行分页,而非基于数据本身的值。 当数据发生插入或删除时,结果集中后续元素的位置会变化,从而导致分页结果异常。


二、具体问题表现#

1. MySQL 中的问题#

数据删除 → 跳过记录

-- 初始数据
SELECT * FROM users ORDER BY id;
-- 1 | A
-- 2 | B
-- 3 | C
-- 4 | D
-- 5 | E
-- 第一页
SELECT * FROM users ORDER BY id LIMIT 0, 2;
-- 1 | A
-- 2 | B
-- 删除 id=2
DELETE FROM users WHERE id = 2;
-- 第二页
SELECT * FROM users ORDER BY id LIMIT 2, 2;
-- 4 | D
-- 5 | E

原因:删除后,原第3条(C)变为第2条,但 LIMIT 2,2 跳过前2条,导致 C 被跳过。

数据新增 → 重复显示

-- 第一页
SELECT * FROM users ORDER BY id LIMIT 0, 2;
-- 1 | A
-- 2 | B
-- 插入 id=1.5 的记录
INSERT INTO users VALUES (1.5, 'A1');
-- 第二页
SELECT * FROM users ORDER BY id LIMIT 2, 2;
-- 2 | B
-- 3 | C

原因:新增数据改变了原有记录的位置,B 被第二页再次包含。


2. Redis Sorted Set(ZSET)中的类似问题#

删除元素 → 跳过

Terminal window
ZADD users 1 "A" 2 "B" 3 "C" 4 "D" 5 "E"
ZRANGE users 0 1
# "A", "B"
ZREM users "B"
ZRANGE users 2 3
# "D", "E"

新增元素 → 重复

Terminal window
ZRANGE users 0 1
# "A", "B"
ZADD users 1.5 "A1"
ZRANGE users 2 3
# "B", "C"

根本原因一致:分页依赖索引位置,而非数据值本身。


三、问题本质总结#

问题类型表现根本原因
数据删除后续记录被跳过偏移量固定,数据前移
数据新增已展示数据重复新数据插入导致原有数据后移

核心缺陷:分页依赖“位置”,而非“数据标识”。


四、解决方案:游标分页(Cursor-based Pagination)#

使用上一页最后一条记录的排序字段值作为下一页起点,避免依赖偏移量。

MySQL 游标分页示例

-- 第一页
SELECT * FROM users ORDER BY id LIMIT 2;
-- 最后一条 id = 2
-- 第二页
SELECT * FROM users WHERE id > 2 ORDER BY id LIMIT 2;
-- 返回 id=3, 4

要求:排序字段需唯一且有序(如自增ID、时间戳)。

Redis 游标分页示例(基于分数)

Terminal window
# 第一页
ZRANGEBYSCORE users -inf +inf LIMIT 0 2
# 假设最大分数为 2
# 第二页
ZRANGEBYSCORE users (2 +inf LIMIT 0 2
# 返回 "C", "D"

使用 (score 表示开区间,避免重复。


五、优势对比#

方式是否受增删影响是否支持跳页适用场景
偏移量分页(LIMIT / ZRANGE)支持数据静态、小规模、需跳页
游标分页(WHERE id > ? / ZRANGEBYSCORE)不支持动态数据、高性能、无限滚动

六、结论#

在数据频繁变动的场景下,应优先使用游标分页(Cursor-based Pagination)替代偏移量分页,以确保分页结果的一致性、完整性与稳定性

MySQL 和 Redis 偏移量分页在数据增删场景下的问题与解决方案
https://hyglgithub.github.io/AstroBlog/posts/20251002/
作者
Wok
发布于
2025-10-02
许可协议
CC BY-NC-SA 4.0