PHP 8.4 分页需用 PDO 预处理+LIMIT/OFFSET,严防 SQL 注入与高偏移性能问题;推荐游标分页替代 OFFSET,URL 参数须用 http_build_query 自动编码。

PHP 8.4 中用 PDO + OFFSET 实现基础分页
PHP 8.4 本身不内置分页类,分页仍是靠 SQL 的 LIMIT 和 OFFSET 配合手动计算。关键不是语言新特性,而是避免在高偏移量下性能崩塌。
常见错误是直接写 LIMIT 20 OFFSET 10000——MySQL 仍要扫描前 10000 行,响应变慢甚至超时。
-
$page必须过滤为正整数:filter_var($page, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]) -
$limit建议固定(如 20),不要让用户传入;防止恶意传limit=999999 - 总条数查询必须独立执行,不能依赖
PDOStatement::rowCount()(对SELECT不可靠)
如何安全计算总页数并避免 SQL injection
拼接 count(*) 查询时,条件必须与主查询完全一致,且所有变量必须用预处理参数绑定。
错误写法:"WHERE status = '" . $_GET['status'] . "'" —— 直接中招 SQL 注入。
立即学习“PHP免费学习笔记(深入)”;
$sqlCount = "SELECT COUNT(*) FROM articles WHERE status = ?"; $stmtCount = $pdo->prepare($sqlCount); $stmtCount->execute([$_GET['status'] ?? 'published']); $total = (int) $stmtCount->fetchColumn();$limit = 20; $page = max(1, (int)($_GET['page'] ?? 1)); $offset = ($page - 1) * $limit;
$sqlList = "SELECT id, title, created_at FROM articles WHERE status = ? ORDER BY id DESC LIMIT ? OFFSET ?"; $stmtList = $pdo->prepare($sqlList); $stmtList->execute([$_GET['status'] ?? 'published', $limit, $offset]); $items = $stmtList->fetchAll(PDO::FETCH_ASSOC);
$totalPages = (int) ceil($total / $limit);
PHP 8.4 下推荐用游标分页替代 OFFSET(尤其数据频繁增删)
当列表按时间/ID 有序且允许“只能往后翻”时,游标分页更稳定。它不依赖行号,而是用上一页最后一条的 id 作为下一页起点。
优势:无 OFFSET 性能衰减、结果不会因中间插入/删除而跳漏或重复。
- 请求示例:
?cursor=12345&limit=20(取id 的最新 20 条) - SQL 必须带
ORDER BY id DESC,且id有索引 - 首次访问无
cursor,则不加WHERE条件,但需记录首条id供后续翻页
分页链接生成时注意 urlencode 查询参数
如果分页 URL 包含中文分类名、标签等,$_GET 参数未编码会导致链接断裂或 400 错误。
例如:category=后端开发 必须变成 category=%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91。
// 正确生成下一页链接 $params = $_GET; $params['page'] = $page + 1; $query = http_build_query($params); $nextUrl = '?' . $query; // 自动处理 urlencode
别手拼 "?page=".($page+1)."&category=".$cat,这是 XSS 和乱码温床。
游标值、分类名、状态码这些动态参数,只要进 URL 就得过 http_build_query 或 urlencode ——PHP 8.4 不会替你做这件事。











