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










