常规分页每次请求执行两条 SQL:
SELECT COUNT(*) FROM table 获取总记录数SELECT * FROM table LIMIT offset, size 获取当前页数据高并发、大数据量下,COUNT(*) 扫描全表极其消耗资源,成为系统吞吐瓶颈。
首次请求查询总数,后续翻页通过 GET 参数传递 total,省去重复统计。
<?php
// 配置
$pageSize = 10;
$page = max(1, (int)($_GET['page'] ?? 1));
// 1. 获取总记录数:首次查询,后续复用
if (isset($_GET['total'])) {
$total = (int)$_GET['total'];
} else {
// 首次加载:执行 COUNT(*)
$sql = "SELECT COUNT(*) AS total FROM `tablename`";
$res = mysql_query($sql) or die(mysql_error());
$row = mysql_fetch_assoc($res);
$total = (int)$row['total'];
}
// 2. 无数据直接返回
if ($total === 0) {
echo '暂无数据';
exit;
}
// 3. 查询当前页数据
$offset = ($page - 1) * $pageSize;
$sql = "SELECT * FROM `tablename` LIMIT $offset, $pageSize";
$res = mysql_query($sql) or die(mysql_error());
// 4. 输出数据
while ($row = mysql_fetch_assoc($res)) {
// 业务渲染...
echo $row['title'] . '<br/>';
}
// 5. 生成分页链接(关键:带上 total 参数)
$baseUrl = "url.php?total=$total&page=";
$pageCount = ceil($total / $pageSize);
// 简单分页 HTML
echo '<div class="pagination">';
for ($i = 1; $i <= $pageCount; $i++) {
$class = ($i === $page) ? ' class="active"' : '';
echo "<a href=\"{$baseUrl}{$i}\"$class>[$i]</a> ";
}
echo '</div>';
?>
| 方案 | 原理 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| URL 传总数 (本文) | 首次查总数,后续透传 | 中小数据量、翻页深度不大 | 实现简单、零改造、兼容性好 | 用户可篡改 total、深翻页仍需 COUNT |
| Redis 缓存总数 | 写入/更新时同步计数器 | 高并发、数据变化频繁 | 毫秒级读取、彻底消除 COUNT | 需维护一致性、引入 Redis 依赖 |
| 延迟加载/无限滚动 | 不显示总页数,仅加载下一页 | 移动端、Feed 流、日志列表 | 无需总数、体验流畅 | 无法跳转指定页、SEO 不友好 |
| 近似总数 | EXPLAIN / 统计表 / 采样估算 | 超大表 (亿级+)、允许误差 | 极快、不锁表 | 非精确、实现复杂 |
| 游标分页 | WHERE id > last_id LIMIT size | ID 单调递增、不需跳页 | 避免 OFFSET 深翻页性能崩塌 | 不支持随机跳页、需唯一有序键 |
INCR/DECR,读取 GET,彻底解决 COUNT 瓶颈。OFFSET 1000000 会扫描 100 万行,改用 WHERE id > $lastId ORDER BY id LIMIT 20。total 合法性(如 min(max($total, 0), $maxPossible))。“首次查总数,后续复用”是零依赖、低侵入的分页优化第一步。结合游标分页、Redis 计数器、前端交互调整,可构建支撑高并发大数据量的高性能分页体系。