Rails / MySQL:Group / Distinct使用LEFT JOINS /慢性能

时间:2017-05-03 14:10:27

标签: mysql ruby-on-rails performance join

我有一个(有点)复杂的查询,返回60K +客户记录。我有两个左外连接,我需要使用查询来搜索相关记录:

customers = customers.
  left_outer_joins(:phones, :emails).
  select("customers.id, customers.name, ...")

if params[:sSearch].present?
  params[:sSearch] = parse_phone_number(params[:sSearch])

  customers = customers.where(
    "customers.name like :search OR
    ..
    phones.number like :search OR
    emails.email like :search",
    search: "%#{params[:sSearch]}%"
  )
end

customers = customers.
  group('customers.id').
  order("#{sort_column} #{sort_direction}).
  page(page).
  per(per_page)

(注意:这是在数据表ajax调用中使用的,因此sort_column,sort_direction,page和per_page都是其中的参数。)

我的所有索引都符合要求。

这是我面临的主要问题:如果我不使用该组('customers.id'),由于左外连接,它将返回重复的客户记录。但添加group子句似乎会将查询时间增加至少2倍。在查询结尾使用.distinct似乎比使用group更慢。

是否有更好/更快的方法不返回带有左外连接的重复项而不显着增加查询时间?现在这需要超过1000毫秒。

编辑:要回答下面的Shadow评论 - 我正在加入多个电话/电子邮件,因为我需要搜索它们。我期望的是,如果客户记录与搜索匹配(比如在加入的手机上),它只返回一个客户 - 而不是两个。

1 个答案:

答案 0 :(得分:1)

如果您只想搜索地址/电话号码,但不想显示它们,请使用带有子查询的exists运算符而不是连接。

sql中的代码看起来像下面这样:

select *
from customers c
where c.name like '%...%'
    or exists (select 1 from emails e where e.email like '%...%' and e.customer_id=c.id) ...

但是,如果您确实要显示地址和电话号码,则必须使用联接。在这种情况下,您可能希望使用MySQL的内置group_concat()函数将各种地址和电话号码连接成一个值。

需要考虑的其他事项:

  1. 使用union代替一系列or条件
  2. 尝试使用全文索引并尽可能搜索而不是like,因为like '%...%'过滤器无法使用索引来加速查询。