不能总用 COUNT() 做分页总数,因其在大数据量高并发下性能差;->paginate(10, false) 仅跳过 COUNT() 但不解决 OFFSET 性能问题;估算行数和游标分页才是高效方案。

为什么不能总用 COUNT(*) 做分页总数?
当表数据超过 50 万行,COUNT(*) 查询会明显变慢,尤其在高并发下容易拖垮数据库连接池。ThinkPHP 的 paginate() 默认强制执行一次 COUNT(*),哪怕你只想要“下一页有没有数据”。这不是框架偷懒,而是它默认要返回 last_page() 和 total() —— 但很多前端其实只关心 hasMore() 和当前页数据。
->paginate(10, false) 是什么,又不是什么?
传入 false 是关闭总数统计,但它只省掉那一条 COUNT(*) SQL,**不解决 LIMIT offset, size 本身的性能问题**。也就是说,第 1000 页(OFFSET 9990)依然会扫描前 9990 行。
- 适用场景:后台日志流、消息列表等“只加载下一页”的接口
- 不适用场景:需要跳转任意页码的搜索页、管理后台的页码输入框
- 注意:
last_page()和total()将返回0或null,别在响应里硬塞"total": 0给前端
用 SHOW TABLE STATUS 估算总行数是否靠谱?
MySQL 的 SHOW TABLE STATUS LIKE 'user' 返回的 Rows 字段是采样估算值,误差通常在 ±10%~30%,但查询耗时几乎为 0。对“显示‘约 23 万条结果’”这种文案完全够用,且能避免锁表或慢查询。
- 在 ThinkPHP 中可封装为辅助方法:
Db::query("SHOW TABLE STATUS LIKE 'user'")[0]['Rows'] - 搭配缓存更稳:
cache('user_total_rows', $rows, 3600) - 不要用于事务强一致场景(如财务对账),但分页展示完全 OK
游标分页 + 估算总数,才是真·高效组合
真正扛住大数据量的方案,是放弃 page= 参数,改用 last_id=,再配合估算总数做前端提示。这样既规避了 OFFSET 性能衰减,又保留了用户对数据规模的感知。
立即学习“PHP免费学习笔记(深入)”;
- 必须确保排序字段有索引且唯一(
id最稳妥,create_time要加id作第二排序条件) -
input('last_id', 0, 'intval')必须校验,否则直接暴露 SQL 注入风险 - 响应里返回
"next_cursor": $list->last()['id'],而不是"next_page": 2
最常被忽略的一点:游标分页和传统分页不能共用同一套缓存 key 设计。用 last_id 的缓存必须包含该值,否则不同用户的“下一页”会互相污染。



















