2011-05-26 4 views
1

J'essaie d'optimiser une requête SQL. Pouvez-vous m'aider?Optimisation de requête SQL complexe

Fondamentalement, chaque utilisateur a des amis à travers une table d'amitié et chaque utilisateur a de nombreux feed_events à travers une table user_feed_events. J'essaye de lister les feed_events des amis d'un utilisateur donné. Ne devrait pas être impossible, non? :)

Comme vous pouvez le voir, les performances de la requête dépendent du nombre d'amis d'un utilisateur. À l'heure actuelle, un utilisateur de 150 amis prend presque 7 secondes à exécuter.

MISE À JOUR: voici comment ma table d'amitié est construite:

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" 

D'abord je demande rails de me donner la liste des ids des userids des amis de l'utilisateur, puis-je utiliser cette chaîne sur la vraie requête.

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" 

J'execute acutally la requête (paginant et limiter à 30 résultats):

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

Mise à jour # 2: EST ICI Explain ANALYSER SORTIE:

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> 

MISE À JOUR # 3: Le problème est que pour mon application rails, j'utilise le plugin has_many_friends (https://github.com/s wemoney/has_many_friends), c'est prendre soin de mes amitiés. Cela fonctionne comme ça. Je suis user_id # 6 et je demande l'amitié à user_id # 10. Quand l'utilisateur # 10 accepte mon amitié une nouvelle rangée est ajoutée à la table avec user_id = 6 et friend_id = 10. Si l'utilisateur # 10 me demande l'amitié le row est: user_id = 10 et friend_id = 6.

Cela signifie que pour trouver friends_by_me je dois rechercher sur "user_id = 6", afin de trouver friends_for_me j'ai besoin de "friend_id = 6". Afin de trouver tous mes amis, je dois rechercher les deux colonnes. Cela rend les jointures très compliquées! Comment géreriez-vous cela?

La seule alternative que je peux penser est:

"(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" 

Mais au moment ne fonctionne pas et je ne suis pas non plus sûr est la bonne façon de le faire!

Merci, Augusto

+0

Veuillez formater vos instructions SQL afin qu'elles puissent être lues sans défilement. –

+0

Ok, je vais le faire maintenant :) – Augusto

+0

Ok, je l'ai formaté. Devrait être mieux maintenant :) – Augusto

Répondre

0

Pourquoi utilisez-vous la liste IN? Pourquoi ne partez-vous pas de l'utilisateur sélectionné? De plus, je pense que votre jointure externe gauche n'est pas nécessaire:

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 

Si vous voulez rester avec votre déclaration originale et juste l'optimiser, puis utilisez ceci:

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 

Cela supprime la gauche inutile REJOIGNEZ .

En outre, assurez-vous que vous avez créé des index sur les colonnes que vous utilisez pour les clés étrangères.

+0

Merci Daniel, comme je l'explique sur la question mise à jour, le problème est de savoir comment la table d'amitié est construite. Pour trouver tous mes amis, je dois rechercher les colonnes user_id et friend_id! Comment puis-je gérer cela dans une jointure? – Augusto

+0

@Augusto: Vous pouvez utiliser la seconde instruction en remplacement direct de votre instruction. Je mettrai à jour la première déclaration pour refléter votre exigence concernant la table d'amitié. –

+0

@Daniel, merci beaucoup. J'utilise maintenant votre première proposition éditée et elle semble plus élégante. Encore, cependant, la requête prend 2 secondes à traiter :(Maintenant je vérifie pour être sûr que j'ai toutes les clés étrangères correctement indexées – Augusto

0

Ok, donc la requête n'est pas votre problème ici, votre base de données doit être configurée pour que cela ne prenne pas plus de quelques microsecondes. D'abord, la requête. Il devrait ressembler à ceci:

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 

Ensuite, vous devez vous assurer que vos colonnes d'identité sont les clés primaires et il y a des indices uniques sur des choses comme (friend_id, user_id) dans la table user_friends. BTW, je viens de faire ces noms, j'ai essayé de deviner ce que vous appeliez la table que vous stockez amitiés.

0
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 

Je ne sais pas si est vraiment nécessaire distincte ici. La requête renvoie les événements de flux pour les amis de l'utilisateur spécifié. Je devrais espérer;)

Modifier. Il se produit que la solution est assez la même que celle proposée par Daniel Hilgarth.

0

Utilisateur un sous-SELECT dans la clause WHERE pour créer une liste d'événements de flux pour un appel IN(). Quelque chose (non testé) comme ceci:

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; 

je serais curieux de voir ce que le EXPLAIN ANALYZE ressemble de cela.