ActiveDataProvider是首选分页方式,因其将分页逻辑下推至SQL层,自动处理count()和LIMIT/OFFSET,避免手动双查;需避免提前执行all()/count(),合理配置pagination和sort,并注意LinkPager参数透传与性能优化。

直接用 ActiveDataProvider 是最省心、最安全的分页方式,90% 的场景都不该绕开它手写 count() + offset + limit。
为什么 ActiveDataProvider 是首选
它把分页逻辑下沉到 SQL 层,query 传进去的是一个未执行的 ActiveQuery 实例,ActiveDataProvider 自己调用 count() 和带 LIMIT/OFFSET 的查询,避免你手动触发两次数据库请求。关键点:
- 不要提前调用
$query->all()或$query->count(),否则会多查一次 -
pagination['pageSize']控制每页条数,默认从$_GET['page']读页码,可改用pageParam指定参数名(如'pageParam' => 'p') - 排序也内置支持:
sort配置里设'defaultOrder' => ['created_at' => SORT_DESC]即可 - 如果模型字段有索引,
count(*)很快;没索引或条件含LIKE '%xxx'时,才需要考虑缓存总数或限制最大页码
LinkPager 链接丢失非分页参数怎么办
默认只保留 page 和 per-page 这类分页参数,category=3、q=search 这类业务参数会被丢掉——这不是 bug,是 Yii2 的设计行为。
解决方法是在 ActiveDataProvider 的 pagination 配置里加 params:
'pagination' => [
'pageSize' => 15,
'params' => ['category' => Yii::$app->request->get('category')],
],
更稳妥的做法是显式列出所有需透传的键,而不是直接用 $_GET(防止注入或意外传入敏感参数)。如果启用了 URL 美化,route 必须写全,比如 'route' => 'article/index',不能省略。
必须手写分页时怎么避开 count() 性能陷阱
比如用了视图、UNION 查询、ES 或其他非 AR 数据源,ActiveDataProvider 不适用,就得自己处理总数和切片。这时最容易踩的坑是 count() 变成全表扫描。
- 先确认 WHERE 条件是否能走索引:主键或高选择性字段上的等值查询,
count(*)通常没问题 - 模糊匹配(
LIKE '%abc')、函数包裹(DATE(created_at))、低基数字段(如status IN (0,1))会让count()变慢 - 替代方案不是换写法,而是控制使用边界:加
maxPages = 1000,超出后隐藏“下一页”按钮;或缓存总数(Cache::get('article_count')),过期时间设为 5 分钟 - 绝对不要在循环里反复查
count(),哪怕只是渲染页码栏
GridView 和 CListView 里的分页自动生效吗
是的,只要传给它们的是带 pagination 配置的 ActiveDataProvider(或 SqlDataProvider),分页就自动启用,不需要额外配置 pager。
但要注意:
-
GridView默认显示分页控件;CListView默认不显示,得在pager选项里显式配'class' => 'CLinkPager' - 如果自定义了
template,要确保包含{pager}占位符,否则控件不会渲染 -
SqlDataProvider要自己提供sql和totalCount,后者不能写成子查询套COUNT(*),否则可能被优化器误判执行计划
复杂分页真正的难点不在代码怎么写,而在于你是否清楚当前查询的 EXPLAIN 结果里有没有 Using filesort 或 Using temporary——这些才是翻到第 1000 页还卡住的根源。


















