技术分享

记一次Laravel分页排查:为什么第一页的数据又出现在第二页?

作者头像 人称外号大脸猫
15 阅读
记一次Laravel分页排查:为什么第一页的数据又出现在第二页?

今天下午,前端同事小张皱着眉头来找我:“哥,商品列表好像有问题啊,怎么翻到第二页又看到第一页的商品了?”

我心里咯噔一下,分页错乱可不是小事。跟着他回到座位,我打开浏览器控制台,仔细查看接口返回的数据。果然,第一页的几个商品,在第二页又诡异出现了。

顺着接口往下查,我看到了这样的代码:

Product::query()->orderBy('category_id')->paginate(10);

乍一看,没问题啊。按分类排序,然后分页,逻辑很清晰。

但我多看了一眼,心里已经明白了七八分。再打开git提交记录,果然印证了我的猜测。我摇摇头,会心一笑,没再多说什么。

今天咱们就单独聊聊这个排序分页的坑。

哪里出了问题?

问题出在排序字段上。category_id是个非唯一字段,意味着多个商品可能属于同一个分类。

数据库遇到多个相同值的记录时,并不保证它们的返回顺序。今天可能按入库顺序排,明天可能按字母顺序排,全看数据库心情。

这就导致了一个致命问题:分页时,某条记录可能既符合第一页的条件,又符合第二页的条件。

来看个例子

假设有这些商品:

ID 商品名 分类ID
1 商品A 1
2 商品B 1
3 商品C 2

每页显示2条,当我们用orderBy('category_id')分页时:

第一页可能是:

  • 商品A(分类1)
  • 商品B(分类1)

第二页可能是:

  • 商品B(分类1)← 怎么又是你?
  • 商品C(分类2)

解决办法

解决方法简单到令人发指:加个第二排序条件就行。

Product::query()
    ->orderBy('category_id')
    ->orderBy('id') // 加上这个
    ->paginate(10);

这样,就算分类ID相同,也会按照ID顺序排列,从此分页稳如老狗。

为什么现在才暴露?

我查了下git记录,这代码已经上线一阵子了。为什么现在才爆出问题?

这跟数据量有关。早期数据少,数据库每次返回顺序都一样,相安无事。随着数据量增加,数据库查询计划变了,顺序就开始飘忽不定了。

这种问题最坑人,平时藏着掖着,等你放松警惕时突然给你来个“惊喜”。

总结一下

  1. 排序要加双保险:用非唯一字段排序时,一定要加个第二条件(比如主键)
  2. 分页要测大数据量:小数据测试通过,不代表大数据量也没问题
  3. 代码审查要细心:这种问题代码审查时很容易放过
  4. 相信同事的反馈:前端同事往往是第一道防线,他们的反馈要重视

这个问题不只Laravel有,凡是涉及数据库排序分页的都会遇到。关键是要明白:数据库不保证相同值的排序顺序,这个特性埋了多少坑啊。

你们团队有没有遇到过这种看似简单,却排查半天的坑?欢迎在评论区分享你的经历。