ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Symfony分页性能优化:绕过COUNT(*)与OFFSET的三大实战方案

Symfony分页性能优化:绕过COUNT(*)与OFFSET的三大实战方案

1. 项目概述:当分页器在百万级数据前开始“喘不过气”

你有没有遇到过这样的场景:后台接口返回一个总条数为327万的用户列表,前端页面点到第892页时,加载时间直接飙到4.7秒,数据库连接池开始报警,运维同事深夜打电话问“是不是又有人写了 select count(*) from huge_table”?这不是段子,是某电商中台团队去年Q3的真实故障复盘记录。而问题的起点,往往就藏在那个看似无害的KnpPaginatorBundle分页器里——它默认用COUNT(*)做总数统计,再用LIMIT OFFSET做数据切片。在小数据量下稳如老狗,一旦数据量突破50万,性能断崖式下跌就成了必然。

这个标题里的“Knp Components分页器”,准确说是 Symfony 生态中广泛使用的KnpPaginatorBundle(其核心组件为 Knp\Component\Paginator),它封装了分页逻辑,让开发者一行代码就能生成分页导航。但它的默认行为,本质上是“为开发体验牺牲运行效率”的典型设计。所谓“终极解决方案”,不是指某个银弹命令,而是一套可组合、可渐进落地、兼顾准确性与响应速度的分页策略体系。它适用于所有使用 Symfony + Doctrine ORM 的中大型项目,尤其适合那些正从单体架构向读写分离演进、或已接入 Elasticsearch 做聚合查询的团队。如果你正在维护一个日活百万、订单表年增千万行的系统,或者正被 DBA 抓着要求优化慢查询,这篇内容就是为你写的——不讲虚的,只说我在三个不同量级项目里实测有效的方案。

2. 核心思路拆解:为什么 COUNT(*) 是性能杀手?OFFSET 又为何越翻越慢?

2.1 COUNT(*) 的真实代价:不只是“多查一次”

很多人以为COUNT(*)慢,是因为“多执行了一条 SQL”。这是严重误解。真正的问题在于:MySQL 在执行COUNT(*)时,必须扫描满足 WHERE 条件的全部行,哪怕你只需要第10001条数据。

举个具体例子。假设你有张order_log表,1200万行,索引结构如下:

CREATE INDEX idx_status_created ON order_log (status, created_at);

现在要查“状态为已完成、按创建时间倒序的第100页(每页20条)”:

SELECT * FROM order_log WHERE status = 'completed' ORDER BY created_at DESC LIMIT 20 OFFSET 1980; -- 第100页 = (100-1)*20 = 1980

这条语句本身可能很快(如果索引覆盖好,毫秒级)。但 KnpPaginatorBundle 默认会先执行:

SELECT COUNT(*) FROM order_log WHERE status = 'completed';

注意:这里没有ORDER BY,也没有LIMIT。MySQL 无法利用idx_status_created索引的created_at部分来加速计数,因为COUNT(*)只关心行数,不关心顺序。它必须遍历status = 'completed'的所有行——而这个条件可能匹配 800 万行。InnoDB 引擎下,这相当于做一次全索引扫描(Index Scan),I/O 和 CPU 开销巨大。

提示:你可以用EXPLAIN FORMAT=JSON查看执行计划,重点关注rows_examined字段。在 800 万匹配行的场景下,这个值往往接近真实匹配数,而非“估算值”。

更糟的是,这个COUNT(*)查询无法被缓存。因为 Doctrine 的二级缓存默认不缓存聚合查询结果,且每次请求的 WHERE 条件参数(如status值)可能不同,导致缓存命中率极低。

2.2 OFFSET 的隐性陷阱:跳过越多,越要“重头数”

LIMIT 20 OFFSET 1980看似简单,但它背后是 MySQL 的“跳过式扫描”机制。引擎必须:

  1. 找到满足WHERE条件的第一行;
  2. 按ORDER BY排序规则,逐行扫描,跳过前 1980 行;
  3. 从第 1981 行开始,取 20 行返回。

关键点在于:跳过的 1980 行,每一行都必须被完整读取、排序、判断是否满足条件。它不是“直接定位到第1981个位置”,而是“从头开始数,数到1981停”。所以页码越大,OFFSET值越大,需要跳过的无效行越多,性能越差。当翻到第5000页(OFFSET 99980)时,MySQL 可能要扫描并丢弃近10万行数据,只为返回20条——这完全是资源浪费。

注意:这个问题在 PostgreSQL 中同样存在,且OFFSET越大,ORDER BY的排序开销也呈线性增长。不要迷信“换数据库就能解决”。

2.3 KnpPaginatorBundle 的默认链路:一条高风险流水线

我们来还原 KnpPaginatorBundle 在大数据量下的完整执行链路(以 Symfony 6.4 + Doctrine ORM 3.0 为例):

  1. Controller 层调用:

    $pagination = $paginator->paginate( $queryBuilder, // Doctrine QueryBuilder 对象 $request->query->getInt('page', 1), // 当前页码 20 // 每页条数 );
  2. Paginator 内部执行:

    • 步骤 A:调用$queryBuilder->getQuery()->getScalarResult()获取总数(即执行COUNT(*));
    • 步骤 B:调用$queryBuilder->setFirstResult($offset)->setMaxResults($limit)生成分页查询(即LIMIT OFFSET);
    • 步骤 C:合并总数、当前页数据、分页元信息(总页数、上一页/下一页链接等)。

问题就出在步骤 A 和步骤 B 是两个完全独立的查询。它们共享同一个WHERE条件,但不共享任何执行上下文。数据库无法“记住”我已经扫描过哪些行,所以第二次查询(LIMIT OFFSET)还得再扫一遍。这造成了双重 I/O 浪费。

2.4 终极方案的核心思想:用“可预测性”替代“精确性”

所谓“终极解决方案”,其底层哲学是:在绝大多数业务场景下,用户并不需要 100% 精确的总页数,他们需要的是快速、稳定、可预期的浏览体验。

  • 用户点“下一页”,他关心的是“能不能看到下20条数据”,而不是“总共还有多少页”;
  • 后台运营人员导出数据,他需要的是“导出全部”,而不是“分页查看全部”;
  • 搜索引擎爬虫抓取,它只关心前几页的高质量内容,不会真的翻到第1000页。

因此,我们的优化不是追求“如何让 COUNT() 更快”,而是思考:“**能否绕过 COUNT()?能否避免 OFFSET?能否用更轻量的方式提供分页能力?**”

答案是肯定的,而且有三条清晰的技术路径:

  • 路径一:游标分页(Cursor-based Pagination)—— 彻底抛弃页码和总数,用“上一页最后一条的排序字段值”作为下一页的起点,性能恒定 O(1);
  • 路径二:估算总数(Estimated Count)—— 用 MySQL 的EXPLAIN估算行数,或采样统计,误差控制在 ±5%,换取毫秒级响应;
  • 路径三:混合分页(Hybrid Pagination)—— 小页码(1-50)用精确分页,大页码(51+)自动降级为游标分页,并隐藏总页数。

这三条路径不是互斥的,而是可以像乐高一样组合。比如,首页和搜索页用路径二(用户需要知道“共找到约120万条结果”),详情流和消息列表用路径一(用户只关心“最新动态”),后台管理页用路径三(管理员偶尔需要跳转到特定页,但大部分时间只看前几页)。

3. 核心细节解析与实操要点:从理论到落地的硬核补丁

3.1 游标分页:告别 OFFSET,拥抱“锚点思维”

游标分页的核心,是把“第 N 页”这个概念,替换成“从某个确定的锚点(cursor)开始往后取 M 条”。这个锚点,通常是上一页最后一条记录的唯一、有序、非空的排序字段值,比如created_at时间戳,或id主键。

为什么created_at比id更常用?
  • id是严格递增的,但业务中常有软删除、异步写入延迟,导致id不完全反映“时间先后”;
  • created_at更符合用户心智模型(“最新发布的”),且只要保证该字段有索引、不为空、类型为DATETIME或BIGINT(时间戳),就能支撑高效查询。
实现一个兼容 KnpPaginatorBundle 的游标分页器

KnpPaginatorBundle 本身不原生支持游标分页,但我们可以通过自定义PaginationAdapterInterface来注入新逻辑。以下是关键代码(已脱敏,可直接用于生产环境):

// src/Pagination/CursorPaginationAdapter.php namespace App\Pagination; use Doctrine\ORM\QueryBuilder; use Knp\Component\Paginator\Pagination\SlidingPagination; use Knp\Component\Paginator\Pagination\SortablePagination; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\......## 1. 项目概述:当分页器在百万级数据前开始“喘不过气” 你有没有遇到过这样的场景:后台接口返回一个总条数为327万的用户列表,前端页面点到第892页时,加载时间直接飙到4.7秒,数据库连接池开始报警,运维同事深夜打电话问“是不是又有人写了 select count(*) from huge_table”?这不是段子,是某电商中台团队去年Q3的真实故障复盘记录。而问题的起点,往往就藏在那个看似无害的 `KnpPaginatorBundle` 分页器里——它默认用 `COUNT(*)` 做总数统计,再用 `LIMIT OFFSET` 做数据切片。在小数据量下稳如老狗,一旦数据量突破50万,性能断崖式下跌就成了必然。 这个标题里的“Knp Components分页器”,准确说是 Symfony 生态中广泛使用的 **KnpPaginatorBundle**(其核心组件为 Knp\Component\Paginator),它封装了分页逻辑,让开发者一行代码就能生成分页导航。但它的默认行为,本质上是“为开发体验牺牲运行效率”的典型设计。所谓“终极解决方案”,不是指某个银弹命令,而是**一套可组合、可渐进落地、兼顾准确性与响应速度的分页策略体系**。它适用于所有使用 Symfony + Doctrine ORM 的中大型项目,尤其适合那些正从单体架构向读写分离演进、或已接入 Elasticsearch 做聚合查询的团队。如果你正在维护一个日活百万、订单表年增千万行的系统,或者正被 DBA 抓着要求优化慢查询,这篇内容就是为你写的——不讲虚的,只说我在三个不同量级项目里实测有效的方案。 ## 2. 核心思路拆解:为什么 COUNT(*) 是性能杀手?OFFSET 又为何越翻越慢? ### 2.1 COUNT(*) 的真实代价:不只是“多查一次” 很多人以为 `COUNT(*)` 慢,是因为“多执行了一条 SQL”。这是严重误解。真正的问题在于:**MySQL 在执行 `COUNT(*)` 时,必须扫描满足 WHERE 条件的全部行,哪怕你只需要第10001条数据**。 举个具体例子。假设你有张 `order_log` 表,1200万行,索引结构如下: ```sql CREATE INDEX idx_status_created ON order_log (status, created_at);

现在要查“状态为已完成、按创建时间倒序的第100页(每页20条)”:

SELECT * FROM order_log WHERE status = 'completed' ORDER BY created_at DESC LIMIT 20 OFFSET 1980; -- 第100页 = (100-1)*20 = 1980

这条语句本身可能很快(如果索引覆盖好,毫秒级)。但 KnpPaginatorBundle 默认会先执行:

SELECT COUNT(*) FROM order_log WHERE status = 'completed';

注意:这里没有ORDER BY,也没有LIMIT。MySQL 无法利用idx_status_created索引的created_at部分来加速计数,因为COUNT(*)只关心行数,不关心顺序。它必须遍历status = 'completed'的所有行——而这个条件可能匹配 800 万行。InnoDB 引擎下,这相当于做一次全索引扫描(Index Scan),I/O 和 CPU 开销巨大。

提示:你可以用EXPLAIN FORMAT=JSON查看执行计划,重点关注rows_examined字段。在 800 万匹配行的场景下,这个值往往接近真实匹配数,而非“估算值”。

更糟的是,这个COUNT(*)查询无法被缓存。因为 Doctrine 的二级缓存默认不缓存聚合查询结果,且每次请求的 WHERE 条件参数(如status值)可能不同,导致缓存命中率极低。

2.2 OFFSET 的隐性陷阱:跳过越多,越要“重头数”

LIMIT 20 OFFSET 1980看似简单,但它背后是 MySQL 的“跳过式扫描”机制。引擎必须:

  1. 找到满足WHERE条件的第一行;
  2. 按ORDER BY排序规则,逐行扫描,跳过前 1980 行;
  3. 从第 1981 行开始,取 20 行返回。

关键点在于:跳过的 1980 行,每一行都必须被完整读取、排序、判断是否满足条件。它不是“直接定位到第1981个位置”,而是“从头开始数,数到1981停”。所以页码越大,OFFSET值越大,需要跳过的无效行越多,性能越差。当翻到第5000页(OFFSET 99980)时,MySQL 可能要扫描并丢弃近10万行数据,只为返回20条——这完全是资源浪费。

注意:这个问题在 PostgreSQL 中同样存在,且OFFSET越大,ORDER BY的排序开销也呈线性增长。不要迷信“换数据库就能解决”。

2.3 KnpPaginatorBundle 的默认链路:一条高风险流水线

我们来还原 KnpPaginatorBundle 在大数据量下的完整执行链路(以 Symfony 6.4 + Doctrine ORM 3.0 为例):

  1. Controller 层调用:

    $pagination = $paginator->paginate( $queryBuilder, // Doctrine QueryBuilder 对象 $request->query->getInt('page', 1), // 当前页码 20 // 每页条数 );
  2. Paginator 内部执行:

    • 步骤 A:调用$queryBuilder->getQuery()->getScalarResult()获取总数(即执行COUNT(*));
    • 步骤 B:调用$queryBuilder->setFirstResult($offset)->setMaxResults($limit)生成分页查询(即LIMIT OFFSET);
    • 步骤 C:合并总数、当前页数据、分页元信息(总页数、上一页/下一页链接等)。

问题就出在步骤 A 和步骤 B 是两个完全独立的查询。它们共享同一个WHERE条件,但不共享任何执行上下文。数据库无法“记住”我已经扫描过哪些行,所以第二次查询(LIMIT OFFSET)还得再扫一遍。这造成了双重 I/O 浪费。

2.4 终极方案的核心思想:用“可预测性”替代“精确性”

所谓“终极解决方案”,其底层哲学是:在绝大多数业务场景下,用户并不需要 100% 精确的总页数,他们需要的是快速、稳定、可预期的浏览体验。

  • 用户点“下一页”,他关心的是“能不能看到下20条数据”,而不是“总共还有多少页”;
  • 后台运营人员导出数据,他需要的是“导出全部”,而不是“分页查看全部”;
  • 搜索引擎爬虫抓取,它只关心前几页的高质量内容,不会真的翻到第1000页。

因此,我们的优化不是追求“如何让 COUNT() 更快”,而是思考:“**能否绕过 COUNT()?能否避免 OFFSET?能否用更轻量的方式提供分页能力?**”

答案是肯定的,而且有三条清晰的技术路径:

  • 路径一:游标分页(Cursor-based Pagination)—— 彻底抛弃页码和总数,用“上一页最后一条的排序字段值”作为下一页的起点,性能恒定 O(1);
  • 路径二:估算总数(Estimated Count)—— 用 MySQL 的EXPLAIN估算行数,或采样统计,误差控制在 ±5%,换取毫秒级响应;
  • 路径三:混合分页(Hybrid Pagination)—— 小页码(1-50)用精确分页,大页码(51+)自动降级为游标分页,并隐藏总页数。

这三条路径不是互斥的,而是可以像乐高一样组合。比如,首页和搜索页用路径二(用户需要知道“共找到约120万条结果”),详情流和消息列表用路径一(用户只关心“最新动态”),后台管理页用路径三(管理员偶尔需要跳转到特定页,但大部分时间只看前几页)。

3. 核心细节解析与实操要点:从理论到落地的硬核补丁

3.1 游标分页:告别 OFFSET,拥抱“锚点思维”

游标分页的核心,是把“第 N 页”这个概念,替换成“从某个确定的锚点(cursor)开始往后取 M 条”。这个锚点,通常是上一页最后一条记录的唯一、有序、非空的排序字段值,比如created_at时间戳,或id主键。

为什么created_at比id更常用?
  • id是严格递增的,但业务中常有软删除、异步写入延迟,导致id不完全反映“时间先后”;
  • created_at更符合用户心智模型(“最新发布的”),且只要保证该字段有索引、不为空、类型为DATETIME或BIGINT(时间戳),就能支撑高效查询。
实现一个兼容 KnpPaginatorBundle 的游标分页器

KnpPaginatorBundle 本身不原生支持游标分页,但我们可以通过自定义PaginationAdapterInterface来注入新逻辑。以下是关键代码(已脱敏,可直接用于生产环境):

// src/Pagination/CursorPaginationAdapter.php namespace App\Pagination; use Doctrine\ORM\QueryBuilder; use Knp\Component\Paginator\Pagination\SlidingPagination; use Knp\Component\Paginator\Pagination\SortablePagination; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\...... // 此处为避免代码过长,已省略重复导入 use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface............ // 此处为避免代码过长,已省略重复导入 use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use Knp\Component\Paginator\Pagination\SortablePaginationTrait; use Knp\Component\Paginator\Pagination\SortablePaginationInterface; use K......
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进