ransack 在使用的时候, 可能需要注意 distinct 的影响
TLDR:
当你涉及搜索的模型比较多的时候(比如 千万), 建议搜索页面初始时不要使用 result(distinct: true). 如果可能, 穷举搜索条件, 在不需要 join 表的搜索避免使用 distinct
废话文学:
在我们使用 ransack 的时候, 我们经常习惯性的弄出一个类似这样的写法
Student.ransack(params[:q]).result(distinct: true)
有可能有人不太清楚为啥这里会有一个 distinct: true
, 有时候似乎不加也没有影响
实际上的原因是 这里是为了防止你在搜索的时候, 搜索的是子条件, 然而满足子条件是需要 join 表的,这样会导致主查询的数据变为多条
举个例子
Students 是学生表 has_many
book_rents
, 表结构为 id, name, gender
BookRents 是借书记录, 记录了学生借出的书名, belongs_to
student
, 表结构为 id, student_id, book_name
假设 搜索 name_eq , 其实加不加 distinct 是没有影响的,因为查询不涉及到 join 表
但是
此时如果查询 借了 “ruby 镐头书” 的学生, 则 搜索条件为. params[q][:bookrentsbooknameeq] = "ruby 镐头书”
如果不加 distinct 则查询记录, 假设某个学生借了两次这本书, 则最终结果就会出现两个相同的 student
官方为了避免沙雕,所以一般情况下,在demo 的时候都默认了加入了 distinct: true,
而大多数人, 选择了直接抄。。。
然而当数据量变大的时候, 问题就变得严重了。
一般在系统中, 初始的搜索页面, 数据量往往是非常巨大的。如果你的初始 index 页面的数据量上了 千万级别后 就会有个非常尴尬的地方
系统会首先读默认的搜索结果
于是一个非常尴尬的事情出现了:
你可能有上亿的数据, 虽然你做了分页, 但是 ransack 会首先执行一次 distinct 你的 id, 再去 count 算总数
这意味着数据库实际上是把整个表的 id 先加载出来再去重, 而这一步, 会比较漫长。
然而实际的情况是, 默认搜索情况下, 是不会涉及到 join 表的, 只可能涉及到展示数据时候 includes 表, 这样 distinct 的运算完全是浪费掉了。
那么怎么解决呢?
对症下药咯, 加个判断, 在默认情况下, result 的 distinct 为 false 或者直接不配置 distinct 即可,
更好的方式是 你需要实现一个 needusingdistinct?(params[:q]) 的方法, 将需要 joins 表的情况都考虑清楚, 这样每次搜索的时候, 就会根据你的查询进行优化, 避免浪费性能
那么, 这么做可以优化多少呢? 额 千万级别的数据, 从原来的查询时间是 几十秒 到现在0.x 秒