从产品设计上用limit优化count
当列表内容很多的时候,分批加载是不得不考虑的设计。
在PC端上,往往会采用分页的设计。但在移动端,有时也会考虑下拉滚动时加载更多。
在程序开发上,各种分页插件几乎都很完善了。
一般都会先执行一个count语句,如果count为0直接结束。
如果count>0再生成一个分页语句。
以java的mybatis-plus为例,甚至提供了count的优化。
例如当你提供一个原始的sql
select 一堆字段 from 表 where 一堆条件 order by 排序
以前我们自己写的话都是直接外层包一个count,变成
select count(*) from (select 一堆字段 from 表 where 一堆条件 order by 排序)
现在的sql parser等基础设施也比较完善,工具库一般都会帮我们优化下sql了, 例如去除order by,去除select的一堆字段,变成
select count(*) from 表 where 一堆条件
前段时间,项目团队里的小伙伴在做性能测试,造了一堆数据。
虽然分页本身确实只查询10条,但是count(*)本身没法优化,只能全部查出来再一条条累加计数。
正在技术层面焦头烂额的时候,我去看了看界面。
发现界面上根本不关注准确的总数,当大于999的时候数量直接显示999+。
加载更多采用的也是滚动下拉,不存在直接跳去某页的操作。
于是我们从业务角度出发对技术实现做了调整。
接口拆分方案
- 接口拆分。将一个接口同时返回total和数据,拆分成一个接口只返回total,另一个接口只返回数据。
// 原始接口 { "total": 3123421, "page": 1, //当前第几页 "size": 10, //每页多少条 "data": [{},{},{},{},{},{},{},{},{},{}] //<=10条数据 } // 拆分后的count接口 { "total" : 1000 //>=1000时返回1000 } //拆分后的数据接口 { "page": 1, "size": 10, "data": [{},{},{},{},{},{},{},{},{},{}] //<=10条数据 }
- 在count前加limit,实现数量<1000时就返回真实数量,>=1000时就返回1000。
select count(*) from (select 1 from 表 where 一堆条件 limit 1000)
- 前端首先调用一次count接口,用于显示预估总数。若为0,可直接省掉数据接口调用。 调用数据接口,若是data元素<size,则可断定当前已是最后一页;否则则可继续加载。
接口不拆分方案
若是不想做接口拆分,可以把分页字段page、size换成hasMore。
如果不想前端改,也可以做如下改动
// 原始接口
{
"total": 1000, //小于1000如实显示,大于1000时默认返回1000
"page": 1, //当前第几页
"size": 10, //每页多少条
"data": [{},{},{},{},{},{},{},{},{},{}] //<=10条数据
}
假设分页已经到了第101页,若返回total=1000则违反常识。
{
"total": 1000, //违反常识
"page": 101,
"size": 10,
"data": [{},{},{},{},{},{},{},{},{},{}] //<=10条数据
}
因此可以增加调整规则,若本次data数量<size,则说明肯定没有更多数据,则
total = (page - 1) * size + data.length
{
"total": 1002,
"page": 101,
"size": 10,
"data": [{},{}] //<10条数据
}
若本地data数量==size,则可能还有更多数据,则
total = page * size + 1
{
"total": 1011,
"page": 101,
"size": 10,
"data": [{},{},{},{},{},{},{},{},{},{}] //<=10条数据
}
产品设计背后的思考
其实我们真的需要准确的总数吗?
不可否认有些时候总数还是有意义的。
但对大多数需要处理的事务性工作来说,你反正是要一条一条处理的,显示999+和321343对你来说真的有区别吗?
如果你觉得本文对你有帮助或不错,可略表心意,请我喝一杯冰可乐。 ☕