paginate() 必须链式调用,正确写法是 Db::name('article')->where($map)->order('id desc')->paginate(15),否则全量查库、总数不准、翻页失效。

paginate() 必须链式调用,不能先 select() 再分页
常见错误是写成 Db::name('article')->where($map)->select()->paginate(15),这会导致全量数据被拉进 PHP 内存,paginate() 只是对数组切片,总数不准、翻页卡顿、WHERE 条件在分页时失效。
正确做法是让 paginate() 完全接管查询链,它会自动发两条 SQL:一条 COUNT(*) 算总数,一条带 LIMIT 查数据。
- 模型写法:
ArticleModel::where($map)->order('id desc')->paginate(15) - 原生查询写法:
Db::name('article')->where($map)->order('id desc')->paginate(15) - 如果用了
join或子查询,确保关联字段有索引,否则COUNT可能变慢
多条件参数必须透传到 query 选项,否则翻页丢失筛选
归档页或搜索页 URL 常带多个参数,比如 ?year=2024&cat=2&status=1。但 paginate() 默认只保留 page 参数,其他全丢——点第二页就回到无筛选的全量数据。
根本原因是没把有效参数显式塞进 query 配置项。不能靠手动拼 URL,也不能依赖 input('page') 干预分页逻辑。
立即学习“PHP免费学习笔记(深入)”;
- 用
array_filter()提前提取并过滤空值:$filter = array_filter(['year' => input('year/d'), 'cat' => input('cat/d'), 'status' => input('status/d', 1)]) -
query中传的 key 要和 URL 参数名一致(如cat),但where条件里可映射为数据库字段(如category_id) - 避免
&cat=&status=这类无效片段,否则链接难看且部分浏览器可能截断
关联查询分页要警惕 COUNT 和 SELECT 结果集不一致
写 UserModel::with('orders')->paginate(10) 表面能跑,但 total() 返回的是用户数,不是「有订单的用户」数;更严重的是,with() 产生的 LEFT JOIN 在 COUNT 阶段被忽略,导致总数虚高、翻页漏数据。
本质是 paginate() 默认对主表 COUNT,而你要分页的是 JOIN 后的结果集。
- 不要用
with()做分页,改用显式join或子查询 - 确保
COUNT和SELECT的WHERE+JOIN逻辑完全一致 - 查「每个用户最新一笔订单」这类场景,必须用子查询兜底,例如先
GROUP BY user_id拿最大时间,再两层JOIN
模板中渲染分页必须用 {$list->render()},不能手写 HTML
很多人在模板里自己拼 <a href="?page=2">下一页</a>,结果参数全丢、样式错乱、移动端适配失败。ThinkPHP 的分页对象已内置 URL 生成和主题配置能力。
render() 会自动读取你传入的 query 参数,并按当前配置输出完整分页 HTML。
- 数据循环用:
{volist name="list" id="vo"}...{/volist} - 总条数用:
{$list->total()},不是count($list) - 若需自定义样式,优先改
setConfig('theme', ...),而不是绕过render()
最易被忽略的一点:query 参数一旦漏传或类型不匹配(比如前端传字符串 "1",后端用 intval() 转成 1 再传回 query),分页链接里的值就会和实际查询条件不一致——页面显示第 2 页,但数据还是第 1 页的。务必保持 query 值与 input 原始值一致。



















