paginate() 必须作用于未执行的查询构造器,否则会假分页;group by 查询需手动处理总数;url参数须透传,否则翻页丢失条件;limit/offset 参数必须强转并校验范围。

paginate() 被调用在已执行查询之后,limit 逻辑根本没进 SQL
最常见的情况是:你写了 UserModel::where('status', 1)->select()->paginate(10),但 select() 已经把 SQL 执行完了,返回的是 PHP 数组或 Collection,paginate() 此时只是对这个数组做“假分页”——它不会生成 LIMIT,也不会查 COUNT(*),总数永远是 0 或错乱,翻页链接也无效。
必须确保 paginate() 作用于未执行的查询构造器:
- ✅ 正确:
UserModel::where('status', 1)->paginate(15) - ✅ 正确(带参数):
UserModel::where('status', 1)->paginate(['list_rows' => 20, 'page' => input('page/d', 1)]) - ❌ 错误:
$data = UserModel::all(); $data->paginate(10)(Collection没有paginate()方法) - ❌ 错误:
UserModel::where('status', 1)->order('id desc')->select()->paginate(10)(select()提前触发查询)
手动写 limit/offset 时参数未强转或越界,MySQL 直接忽略或报错
如果你绕过 paginate(),直接用 limit() 和 offset()(比如导出、后台批量拉取),$_GET['offset'] 或 input('offset') 若未经处理就喂给数据库,极易导致 LIMIT 失效或 SQL 报错。
关键动作只有三步,缺一不可:
- 用
input('offset/d', 0)强制转整型,避免字符串注入 - 立刻截断范围:
$offset = min(max(0, $offset), 100000),硬性限制最大偏移量 - 确保
limit值也是安全整数:Db::name('user')->limit((int)$size)->offset((int)$offset)->select()
别信“前端传的都是合法值”——只要 URL 里写 ?offset=abc 或 ?offset=9999999999,没做校验就会崩。
GROUP BY + paginate() 导致 total() 返回 0,分页链接不显示
paginate() 底层依赖 COUNT(*) 获取总数,但带 GROUP BY 的查询无法直接套用原表 COUNT。例如:Db::table('log')->field('DATE_FORMAT(create_time,"%Y-%m") as date')->group('date')->paginate(10),框架会尝试执行 SELECT COUNT(*) FROM log GROUP BY date,这在 MySQL 中非法,total() 返回 0,整个分页对象为空。
两种可靠解法:
- 改用
page()+select()手动分页:$list = $query->page($page, $size)->select(),再自行计算总数(如先查SELECT COUNT(DISTINCT DATE_FORMAT(create_time,"%Y-%m"))) - 用子查询包裹 GROUP BY 结果,再对其分页(需确认 MySQL 版本支持):
Db::query("SELECT * FROM ({$subSql}) AS tmp LIMIT ?, ?", [$start, $size])
注意:field() 如果漏掉主键或唯一字段,也可能干扰 COUNT 推导,尽量显式写出分组依据字段。
URL 参数未透传,翻页后条件丢失 → 数据变空
前后端分离下,搜索接口带 ?keyword=张三&status=1,但点击第 2 页时 URL 变成 ?page=2,筛选条件没了——这不是 limit 无效,而是查询条件被丢弃,导致 paginate() 查的是全表空结果。
必须显式把当前所有 GET 参数透传进分页器:
- ✅ 正确:
->paginate(['list_rows' => 20, 'query' => request()->param()]) - ✅ 追加固定参数:
$list->appends(['module' => 'admin'])(必须在 assign 前调用) - ❌ 错误:
->paginate(20, true, ['query' => request()->param()])(TP6 不接受第三个参数为数组,会静默失效)
如果用 POST 提交搜索条件,分页仍走 GET,那就必须让前端把所有筛选参数拼进分页链接 query string,否则翻页即重置。
真正难排查的不是 limit 写错,而是 paginate() 表面调用了,实则早已脱离查询链路;或者总数算不出来,却以为是 limit 没生效。盯住 total() 返回值、看实际执行的 SQL(开启 debug)、确认参数是否进到了最终查询,比反复调 limit() 有用得多。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











