优化Mysql中的rank排序查询
公司项目有一个签到功能,其中可以显示当前用户的连续签到次数在所有人中的排名。
因为之前用户表只有几万数据,所以使用的算法是:直接取出按连续签到次数排序好的用户ID数组,然后查找用户ID在其中的index,再加1即可。数据量不大的情况下,这么做还好。不过随着时间推移,现在有了40w的用户数据……
每天Mysql慢查询中,这块的sql都刷屏,所以决定对其进行优化。
未改动之前的代码如下:
ranks = Member.order('serial_checkins desc, id desc').pluck(:id)
@rank = ranks.index(current_member.id) + 1
Mysql木有rank函数,而用变量的方式模拟rank函数需要嵌套子查询,效能会更烂。所以最先想到的是对ranks数组改用二分查找,于是不假思索的操刀就开干了。
ranks = Member.order('serial_checkins desc, id desc').pluck(:id)
@rank = ranks.bsearch {|n| n >= current_member.id} + 1
跑了下测试,发现没通过……琢磨了一下,想到原因是:这个用户ID数组必然是乱序的,所以不能使用二分查找OTL
琢磨了几分钟,想到了一个曲线救国的方案:
# count所有连续签到次数高于当前用户的用户总数
upper_count = Member.where('serial_checkins > ?', current_member.serial_checkins).count
# 取出所有连续签到次数和当前用户一样的,升序排列后的用户ID组
sblings = Member.where(serial_checkins: current_member.serial_checkins).order(id: :asc).pluck(:id)
# 对这个ID组进行二分查找,找到当前用户的index值
sblings_count = sblings.bsearch_index {|n| n >= current_member.id} + 1
# 两个count相加……
@rank = upper_count + sblings_count
因为使用这个功能频繁的人大多处于签到排行头部集团,所以这一算法取出的2项数据量都很小,速度会非常快。 如果用户处于非头部集团,那么同排位的人可能很多,用二分查找和原来比较也能大大提升效率。:w