Оптимизация сложных SQL-запросов - PullRequest
1 голос
/ 26 мая 2011

Я пытаюсь оптимизировать запрос SQL. Вы можете мне помочь?

По сути, у каждого пользователя есть друзья через таблицу дружбы, и у каждого пользователя есть много feed_events через таблицу user_feed_events. Я пытаюсь перечислить feed_events друзей данного пользователя. Не должно быть невозможным, верно? :)

Как видите, производительность запроса зависит от того, сколько друзей у пользователя. Сейчас пользователю с 150 друзьями требуется почти 7 секунд для выполнения.

ОБНОВЛЕНИЕ: вот как строится моя таблица дружбы:

create_table "friendships", :force => true do |t|
t.integer  "user_id",     :null => false
t.integer  "friend_id",   :null => false
t.datetime "created_at"
t.datetime "accepted_at"
end

add_index "friendships", ["friend_id"], :name => "index_friendships_on_friend_id"
add_index "friendships", ["user_id"], :name => "index_friendships_on_user_id"

Сначала я прошу rails дать мне список идентификаторов userids друзей пользователя, затем я использую эту строку в реальном запросе.

friends_id = current_user.friends.collect {|f| f.id}.join(",")

sql = "
SELECT 
DISTINCT feed_events.id, 
feed_events.event_type, 
feed_events.type_id, 
feed_events.data, 
feed_events.created_at, 
feed_events.updated_at, 
user_feed_events.user_id  
FROM feed_events 
LEFT JOIN user_feed_events 
ON feed_events.id = user_feed_events.feed_event_id 
WHERE user_feed_events.user_id IN (#{friends_id}) 
ORDER BY feed_events.created_at DESC"

Затем я тщательно выполняю запрос (разбив его на страницы и ограничиваясь 30 результатами):

@events = FeedEvent.paginate_by_sql(sql, :page => params[:page], :per_page => 30)

ОБНОВЛЕНИЕ № 2: ЗДЕСЬ ОБЪЯСНЕНИЕ АНАЛИЗА:

    SQL> EXPLAIN ANALYZE (SELECT  DISTINCT feed_events.id,  feed_events.event_type,  feed_events.type_id,  feed_events.data,  feed_events.created_at,  feed_events.updated_at,  user_feed_events.user_id   FROM user_feed_events  INNER JOIN feed_events  ON feed_events.id = user_feed_events.feed_event_id  WHERE user_feed_events.user_id IN (1,7,9,8,14,15,20,35,40,39,41,42,57,84,98,109,121,74,129,64,137,77,172,182,206,201,284,31,94,232,311,168,30,114,50,174,419,403,438,464,423,513,351,349,385,622,751,359,809,838,844,962,831,786,896,1001,992,998,990,256,67,623,957,1226,1060,1009,1490,132,1467,1672,619,1459,1466,993,1599,1365,607,1381,1714,1154,2032,2230,2240,2354,598,2345,1804,634,1900,2652,1975,2164,1759,3288,1004,3487,3507,3542,3566,514,3787,3137,3803,3090,4012,855,17,2026,1463,335,1000,935,5,12,10,13,19,18,16,22,34,27,29,59,126,90,46,23,63,291,134,229,107,439,521)  ORDER BY feed_events.created_at DESC)
    +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    |                                                                                                                                                                                                                                                                                                          QUERY PLAN                                                                                                                                                                                                                                                                                                          |
    +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | Unique  (cost=6090.87..6162.93 rows=18014 width=389) (actual time=1641.210..1733.010 rows=29691 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
    |   ->  Sort  (cost=6090.87..6099.88 rows=18014 width=389) (actual time=1641.206..1670.882 rows=29694 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
    |         Sort Key: feed_events.created_at, feed_events.id, feed_events.event_type, feed_events.type_id, feed_events.data, feed_events.updated_at, user_feed_events.user_id                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    |         Sort Method:  quicksort  Memory: 17755kB                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
    |         ->  Hash Join  (cost=3931.63..5836.21 rows=18014 width=389) (actual time=258.541..361.345 rows=29694 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
    |               Hash Cond: (user_feed_events.feed_event_id = feed_events.id)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    |               ->  Bitmap Heap Scan on user_feed_events  (cost=926.64..2745.66 rows=18014 width=8) (actual time=6.930..42.367 rows=29694 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
    |                     Recheck Cond: (user_id = ANY ('{1,7,9,8,14,15,20,35,40,39,41,42,57,84,98,109,121,74,129,64,137,77,172,182,206,201,284,31,94,232,311,168,30,114,50,174,419,403,438,464,423,513,351,349,385,622,751,359,809,838,844,962,831,786,896,1001,992,998,990,256,67,623,957,1226,1060,1009,1490,132,1467,1672,619,1459,1466,993,1599,1365,607,1381,1714,1154,2032,2230,2240,2354,598,2345,1804,634,1900,2652,1975,2164,1759,3288,1004,3487,3507,3542,3566,514,3787,3137,3803,3090,4012,855,17,2026,1463,335,1000,935,5,12,10,13,19,18,16,22,34,27,29,59,126,90,46,23,63,291,134,229,107,439,521}'::integer[]))     |
    |                     ->  Bitmap Index Scan on index_user_feed_events_on_user_id  (cost=0.00..925.74 rows=18014 width=0) (actual time=6.836..6.836 rows=29694 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
    |                           Index Cond: (user_id = ANY ('{1,7,9,8,14,15,20,35,40,39,41,42,57,84,98,109,121,74,129,64,137,77,172,182,206,201,284,31,94,232,311,168,30,114,50,174,419,403,438,464,423,513,351,349,385,622,751,359,809,838,844,962,831,786,896,1001,992,998,990,256,67,623,957,1226,1060,1009,1490,132,1467,1672,619,1459,1466,993,1599,1365,607,1381,1714,1154,2032,2230,2240,2354,598,2345,1804,634,1900,2652,1975,2164,1759,3288,1004,3487,3507,3542,3566,514,3787,3137,3803,3090,4012,855,17,2026,1463,335,1000,935,5,12,10,13,19,18,16,22,34,27,29,59,126,90,46,23,63,291,134,229,107,439,521}'::integer[])) |
    |               ->  Hash  (cost=2848.84..2848.84 rows=44614 width=385) (actual time=251.490..251.490 rows=44663 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    |                     ->  Seq Scan on feed_events  (cost=0.00..2848.84 rows=44614 width=385) (actual time=0.035..77.044 rows=44663 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | Total runtime: 1780.200 ms                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    SQL>

ОБНОВЛЕНИЕ # 3 : Проблема в том, что для моего приложения rails я использую плагин has_many_friends (https://github.com/swemoney/has_many_friends),, который заботится о моей дружбе. Он работает следующим образом. Я user_id # 6, и я прошу дружбу с user_id # 10. Когда пользователь # 10 принимает мою дружбу, в таблицу добавляется новая строка с user_id = 6 и friend_id = 10. Если пользователь # 10 спрашивает меня о дружбе, строка выглядит так: user_id = 10 и friend_id = 6.

Это означает, что для того, чтобы найти friends_by_me, мне нужно выполнить поиск по "user_id = 6", а для поиска friends_for_me мне нужно "friend_id = 6". Для того, чтобы найти всех моих друзей, мне нужно найти обе колонки. Это очень усложняет создание соединений! Как бы вы справились с этим?

Единственная альтернатива, о которой я могу подумать:

"(SELECT 
DISTINCT feed_events.id, 
feed_events.event_type, 
feed_events.type_id, 
feed_events.data, 
feed_events.created_at, 
feed_events.updated_at, 
user_feed_events.user_id 
FROM feed_events 
INNER JOIN user_feed_events 
ON feed_events.id = user_feed_events.feed_event_id 
INNER JOIN friendships 
ON user_feed_events.user_id = friendships.user_id 
WHERE friendships.user_id = 6 
AND friendships.accepted_at IS NOT NULL)

UNION DISTINCT

(SELECT 
DISTINCT additional_feed_events.id, 
additional_feed_events.event_type, 
additional_feed_events.type_id, 
additional_feed_events.data, 
additional_feed_events.created_at, 
additional_feed_events.updated_at, 
user_feed_events.user_id 
FROM feed_events AS additional_feed_events 
INNER JOIN user_feed_events 
ON additional_feed_events.id = user_feed_events.feed_event_id 
INNER JOIN friendships 
ON user_feed_events.user_id = friendships.friend_id 
WHERE friendships.friend_id = 6 
AND friendships.accepted_at IS NOT NULL) 

ORDER BY feed_events.created_at DESC"

Но на данный момент не работает, и я также не уверен, что это правильный способ!

Спасибо, Augusto

Ответы [ 4 ]

0 голосов
/ 26 мая 2011

Пользователь sub- SELECT в предложении WHERE создает список событий фида для вызова IN().Что-то (не проверено), как это:

SELECT fe.id, 
    fe.event_type, 
    fe.type_id, 
    fe.data, 
    fe.created_at, 
    fe.updated_at,
    ufe.user_id  
FROM feed_events AS fe, user_feed_events AS ufe
WHERE TRUE = TRUE
    AND fe.id = ufe.feed_event_id
    AND ufe.user_id = :user_id
    AND fe.id IN((
        SELECT ufe.feed_event_id
        FROM user_feed_events AS ufe, user_friends AS uf
        WHERE uf.friend_id = :user_id
    ))
ORDER BY feed_events.created_at DESC;

Мне было бы интересно посмотреть, как выглядит EXPLAIN ANALYZE из этого.

0 голосов
/ 26 мая 2011

Хорошо, значит, запрос здесь не является вашей проблемой, ваша база данных должна быть настроена так, чтобы это занимало не более нескольких микросекунд.Сначала, хотя, запрос.Это должно выглядеть следующим образом:

 SELECT feed_events.id, 
        feed_events.event_type, 
        feed_events.type_id, 
        feed_events.data, 
        feed_events.created_at, 
        feed_events.updated_at, 
        user_feed_events.user_id  

   FROM feed_events
            INNER JOIN
        user_feed_events ON feed_events.id = user_feed_events.feed_event_id
            INNER JOIN
        user_friends     ON user_friends.friend_id = user_feed_events.user_id

  WHERE user_friends.user_id = ** The Id of the User in Question **
  ORDER BY feed_events.created_at DESC

Далее необходимо убедиться, что столбцы Id являются первичными ключами, а в таких таблицах, как (friend_id, user_id), есть уникальные индексы.Кстати, я только что придумал эти имена, я попытался угадать, как вы называете таблицу, в которой вы храните дружбу.

0 голосов
/ 26 мая 2011
select distinct fe.id, fe.event_type,
       fe.type_id, fe.data, fe.created_at,
       fe.updated_at, ufe.user_id
from friendships as f
    inner join user_feed_events as ufe on f.friend_id = ufe.user_id
    inner join feed_events as fe on ufe.user_id = fe.id
where f.user_id = 6 and f.accepted_at is not null
order by fe.created_at desc

Не уверен, действительно ли здесь нужно отдельное.Запрос возвращает события фида для друзей указанного пользователя. Надеюсь;)

Редактировать.Оказалось, что решение почти такое же, как предложенный Дэниелом Хилгартом.

0 голосов
/ 26 мая 2011

Почему вы используете список IN? Почему бы вам не начать с выбранного пользователя? Кроме того, я думаю, что ваше левое внешнее соединение не нужно:

SELECT 
DISTINCT feed_events.id, 
feed_events.event_type, 
feed_events.type_id, 
feed_events.data, 
feed_events.created_at, 
feed_events.updated_at, 
user_feed_events.user_id  
FROM 
(
  select friend_id from friendship where user_id = YOURUSER
  UNION
  select user_id as friend_id from friendship where friend_id = YOURUSER
) friendship
inner join user_feed_events 
on friendship.friend_id = user_feed_events.user_id
inner join feed_events
on user_feed_events.feed_event_id = feed_events.id
ORDER BY feed_events.created_at DESC

Если вы хотите сохранить исходное утверждение и просто оптимизировать его, используйте следующее:

SELECT 
DISTINCT feed_events.id, 
feed_events.event_type, 
feed_events.type_id, 
feed_events.data, 
feed_events.created_at, 
feed_events.updated_at, 
user_feed_events.user_id  
FROM user_feed_events 
INNER JOIN feed_events 
ON feed_events.id = user_feed_events.feed_event_id 
WHERE user_feed_events.user_id IN (#{friends_id}) 
ORDER BY feed_events.created_at DESC

Это удаляет ненужное левое соединение.

Кроме того, убедитесь, что вы создали индексы для столбцов, которые вы используете для внешних ключей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...