PERF: Avoid unnecessary expensive joins if possible.

```
EXPLAIN ANALYZE SELECT  "topics".* FROM "topics"
LEFT JOIN topic_users tu ON topics.id = tu.topic_id AND tu.user_id =
13455
WHERE ("topics"."deleted_at" IS NULL)
AND (topics.archetype = 'private_message')
AND (
  topics.id IN (
    SELECT topic_id
    FROM topic_allowed_groups tg
    JOIN group_users gu ON gu.user_id = 13455 AND gu.group_id =
tg.group_id
    WHERE gu.group_id IN (47)
  )
)
AND (
  topics.id IN (
    SELECT ta.topic_id
    FROM topic_allowed_users ta
    WHERE ta.user_id IN (32852,-10)
  )
  OR
  topics.id IN (
    SELECT tg.topic_id
    FROM topic_allowed_groups tg
    WHERE tg.group_id IN (-10)
  )
)
AND (topics.id NOT IN (69933,69995,69988,69984,69968,69973,69971,69952))
AND "topics"."visible" = 't'
ORDER BY topics.bumped_at DESC
LIMIT 3;
```

Planning time: 1.277 ms
Execution time: 71.577 ms

```
EXPLAIN ANALYZE SELECT  "topics".* FROM "topics"
LEFT JOIN topic_users tu ON topics.id = tu.topic_id AND tu.user_id =
13455
LEFT JOIN (
  SELECT * FROM topic_allowed_groups _tg
  LEFT JOIN group_users gu
  ON gu.user_id = 13455
  AND gu.group_id = _tg.group_id
  AND gu.group_id IN (47)
) tg ON topics.id = tg.topic_id
LEFT JOIN topic_allowed_users ta2 ON topics.id = ta2.topic_id AND
ta2.user_id IN (32852)
WHERE ("topics"."deleted_at" IS NULL)
AND (topics.archetype = 'private_message')
AND (tg.topic_id IS NOT NULL)
AND (ta2.topic_id IS NOT NULL)
AND (topics.id NOT IN (69933,69995,69988,69984,69968,69973,69971,69952))
AND "topics"."visible" = 't'
ORDER BY topics.bumped_at DESC
LIMIT 3;
```

Planning time: 1.191 ms
Execution time: 0.129 ms
This commit is contained in:
Guo Xiang Tan 2017-09-14 11:12:59 +08:00
parent 9dc5bf1d97
commit f3fadf41b7

View File

@ -748,53 +748,60 @@ class TopicQuery
end end
def related_messages_user(params) def related_messages_user(params)
user_ids = ((params[:target_user_ids] || []) << -10) messages = messages_for_user.limit(params[:count])
user_ids = ActiveRecord::Base.send(:sanitize_sql_array, user_ids.join(',')) messages = allowed_messages(messages, params)
group_ids = (((params[:target_group_ids] - params[:my_group_ids]) || []) << -10)
group_ids = ActiveRecord::Base.send(:sanitize_sql_array, group_ids.join(','))
result = messages_for_user
.limit(params[:count])
.joins("
LEFT JOIN topic_allowed_users ta2
ON topics.id = ta2.topic_id
AND ta2.user_id IN (#{user_ids})",
)
.joins("
LEFT JOIN topic_allowed_groups tg
ON topics.id = tg.topic_id
AND tg.group_id IN (#{group_ids})",
)
.where("ta2.topic_id IS NOT NULL OR tg.topic_id IS NOT NULL")
end end
def related_messages_group(params) def related_messages_group(params)
messages_for_groups_or_user(params[:my_group_ids]) messages = messages_for_groups_or_user(params[:my_group_ids]).limit(params[:count])
.limit(params[:count]) messages = allowed_messages(messages, params)
.where('topics.id IN ( end
SELECT ta.topic_id
FROM topic_allowed_users ta
WHERE ta.user_id IN (:user_ids)
) OR
topics.id IN (
SELECT tg.topic_id
FROM topic_allowed_groups tg
WHERE tg.group_id IN (:group_ids)
)
', user_ids: (params[:target_user_ids] || []) + [-10],
group_ids: ((params[:target_group_ids] - params[:my_group_ids]) || []) + [-10])
def allowed_messages(messages, params)
user_ids = (params[:target_user_ids] || [])
group_ids = ((params[:target_group_ids] - params[:my_group_ids]) || [])
if user_ids.present?
messages =
messages.joins("
LEFT JOIN topic_allowed_users ta2
ON topics.id = ta2.topic_id
AND ta2.user_id IN (#{sanitize_sql_array(user_ids)})
")
end
if group_ids.present?
messages =
messages.joins("
LEFT JOIN topic_allowed_groups tg2
ON topics.id = tg2.topic_id
AND tg2.group_id IN (#{sanitize_sql_array(group_ids)})
")
end
messages =
if user_ids.present? && group_ids.present?
messages.where("ta2.topic_id IS NOT NULL OR tg2.topic_id IS NOT NULL")
elsif user_ids.present?
messages.where("ta2.topic_id IS NOT NULL")
elsif group_ids.present?
messages.where("tg2.topic_id IS NOT NULL")
end
end end
def messages_for_groups_or_user(group_ids) def messages_for_groups_or_user(group_ids)
if group_ids.present? if group_ids.present?
base_messages base_messages
.where('topics.id IN ( .joins("
SELECT topic_id LEFT JOIN (
FROM topic_allowed_groups tg SELECT * FROM topic_allowed_groups _tg
JOIN group_users gu ON gu.user_id = :user_id AND gu.group_id = tg.group_id LEFT JOIN group_users gu
WHERE gu.group_id IN (:group_ids) ON gu.user_id = #{@user.id.to_i}
)', user_id: @user.id, group_ids: group_ids) AND gu.group_id = _tg.group_id
AND gu.group_id IN (#{sanitize_sql_array(group_ids)})
) tg ON topics.id = tg.topic_id
")
.where("tg.topic_id IS NOT NULL")
else else
messages_for_user messages_for_user
end end
@ -851,4 +858,10 @@ class TopicQuery
result.order('topics.bumped_at DESC') result.order('topics.bumped_at DESC')
end end
private
def sanitize_sql_array(input)
ActiveRecord::Base.send(:sanitize_sql_array, input.join(','))
end
end end