MySQL 和 Redis 偏移量分页在数据增删场景下的问题与解决方案
一、问题背景:偏移量分页(Offset-based Pagination)的固有缺陷
无论是 MySQL 的 LIMIT offset, size
,还是 Redis 的 ZRANGE key start stop
,它们都基于结果集中的位置(偏移量)进行分页,而非基于数据本身的值。
当数据发生插入或删除时,结果集中后续元素的位置会发生变化,从而导致分页结果异常。
二、具体问题表现
1. MySQL 中的问题
(1)数据删除 → 跳过记录
-- 初始数据
SELECT * FROM users ORDER BY id;
-- id | name
-- ---+-----
-- 1 | A
-- 2 | B
-- 3 | C
-- 4 | D
-- 5 | E-- 第一页(LIMIT 0,2)
SELECT * FROM users ORDER BY id LIMIT 0, 2;
-- 1 | A
-- 2 | B-- 删除 id=2
DELETE FROM users WHERE id = 2;-- 第二页(LIMIT 2,2)
SELECT * FROM users ORDER BY id LIMIT 2, 2;
-- 4 | D ← 跳过了 id=3(C)
-- 5 | E
✅ 原因:删除后,原第3条(C)变为第2条,但 LIMIT 2,2
跳过了前2条,直接从第3条开始取,导致 C 被跳过。
(2)数据新增 → 重复显示
-- 第一页
SELECT * FROM users ORDER BY id LIMIT 0, 2;
-- 1 | A
-- 2 | B-- 新增一条 id=1.5 的记录(按 id 排序插入中间)
INSERT INTO users VALUES (1.5, 'A1');-- 第二页
SELECT * FROM users ORDER BY id LIMIT 2, 2;
-- 2 | B ← 已在第一页出现,现在又出现在第二页
-- 3 | C
✅ 原因:新增数据改变了原有记录的位置,B 从第2位变为第3位,被第二页再次包含。
2. Redis Sorted Set(ZSET)中的类似问题
(1)删除元素 → 跳过
# 初始
ZADD users 1 "A" 2 "B" 3 "C" 4 "D" 5 "E"# 第一页
ZRANGE users 0 1
# "A", "B"# 删除 "B"
ZREM users "B"# 第二页
ZRANGE users 2 3
# "D", "E" ← 跳过了 "C"
(2)新增元素 → 重复
# 第一页
ZRANGE users 0 1 → "A", "B"# 新增分数 1.5 的 "A1"
ZADD users 1.5 "A1"# 第二页
ZRANGE users 2 3 → "B", "C" ← "B" 重复出现
✅ 根本原因一致:ZRANGE
基于索引位置,而非分数或成员值本身。
三、问题本质总结
问题类型 | 表现 | 根本原因 |
---|---|---|
数据删除 | 后续记录被跳过 | 偏移量固定,但数据前移 |
数据新增 | 已展示数据重复出现 | 新数据插入导致原有数据后移,被后续页再次包含 |
🔍 核心缺陷:分页依赖“位置”,而非“数据标识”。
四、解决方案:游标分页(Cursor-based Pagination)
使用上一页最后一条记录的排序字段值作为下一页的起点,避免依赖偏移量。
✅ MySQL 游标分页示例
-- 第一页
SELECT * FROM users ORDER BY id LIMIT 2;
-- 假设最后一条 id = 2-- 第二页:从 id > 2 开始查
SELECT * FROM users WHERE id > 2 ORDER BY id LIMIT 2;
-- 返回 id=3(C), 4(D) —— 不受中间增删影响
要求:排序字段需唯一且有序(如自增ID、时间戳等)。
✅ Redis 游标分页示例(基于分数)
# 第一页:取分数最小的2个
ZRANGEBYSCORE users -inf +inf LIMIT 0 2
# 假设最大分数为 2("B")# 第二页:从分数 > 2 开始取
ZRANGEBYSCORE users (2 +inf LIMIT 0 2
# 返回 "C"(3), "D"(4)
使用
(score
表示“开区间”,避免重复。
五、优势对比
方式 | 是否受增删影响 | 是否支持跳页 | 适用场景 |
---|---|---|---|
偏移量分页(LIMIT / ZRANGE) | ❌ 是 | ✅ 支持 | 数据静态、小规模、需跳页 |
游标分页(WHERE id > ? / ZRANGEBYSCORE) | ✅ 否 | ❌ 不支持(只能顺序翻页) | 动态数据、高性能、无限滚动 |
六、结论
在数据频繁变动的场景下,应优先使用游标分页(Cursor-based Pagination)替代传统的偏移量分页,以确保分页结果的一致性、完整性与稳定性。