
本文介绍一种不依赖连续 id 的 mysql 双向分页方案:通过记录边界 id 并结合 `array_reverse()` 实现稳定、高效、容错的上一页/下一页导航,完美解决因数据删除导致 id 不连续带来的翻页错乱问题。
在基于 ID 的分页实践中,常见的“游标分页”(Cursor-based Pagination)通常采用 WHERE id 上一页却容易陷入误区:若简单使用 WHERE id 当前页起始 ID 为下界进行反向查询。
✅ 正确思路是:
- 下一页:WHERE id
- 上一页:WHERE id > $first_id LIMIT 21 → 获取更大 ID 的数据(自然升序),再反转数组,使其视觉顺序与用户预期一致(即顶部显示 ID=99,底部显示 ID=120)。
以下是完整 PHP + MySQL 实现示例(含关键注释):
prepare("SELECT * FROM table_name WHERE id > ? LIMIT ?");
$stmt->execute([$_GET['prev'], $limit]);
$results = array_reverse($stmt->fetchAll(PDO::FETCH_ASSOC));
} elseif ($isNext) {
// 下一页:查比 $last_id 更小的 21 条(降序)
$stmt = $db->prepare("SELECT * FROM table_name WHERE id < ? ORDER BY id DESC LIMIT ?");
$stmt->execute([$_GET['next'], $limit]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
// 首页:取最大的 21 条
$stmt = $db->prepare("SELECT * FROM table_name ORDER BY id DESC LIMIT ?");
$stmt->execute([$limit]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 渲染数据(假设按 id 降序展示)
foreach ($results as $row) {
echo "ID: {$row['id']} | {$row['title']}";
}
// 生成分页按钮(关键:传递正确的边界 ID)
$firstId = !empty($results) ? $results[0]['id'] : null;
$lastId = !empty($results) ? end($results)['id'] : null;
// 上一页按钮:传当前页第一条记录的 ID(即上一页的「最小 ID」应 > 它)
if ($firstId !== null) {
echo '← 上一页';
}
// 下一页按钮:传当前页最后一条记录的 ID(即下一页的「最大 ID」应 < 它)
if ($lastId !== null && count($results) === $limit) {
echo '下一页 →';
}
?>⚠️ 重要注意事项:
- 必须确保 id 字段有索引(如主键或唯一索引),否则 WHERE id > ? 查询将全表扫描,性能急剧下降;
- 避免混合使用 OFFSET:传统 LIMIT offset, size 在大数据量下效率低下,且无法精准支持“上一页”逻辑;
- 边界 ID 必须来自当前页真实数据:切勿用计算(如 $firstId - 21),因 ID 不连续;
- 前端需禁用重复提交:防止用户快速点击导致状态错乱;
- 考虑添加 created_at 辅助排序:若存在同 ID 冲突(极罕见),可加二级排序保障稳定性,如 ORDER BY id DESC, created_at DESC。
该方案彻底摆脱对 ID 连续性的依赖,即使中间大量删除数据,翻页依然精准、响应迅速,是高并发、大数据量场景下的推荐实践。










