2009-02-13 23 views
0

J'ai une table de mots-clés que je rafraîchis régulièrement par rapport à une API de recherche à distance, et j'ai une autre table qui reçoit une rangée chaque fois que je rafraîchis l'un des mots-clés. J'utilise cette table pour empêcher plusieurs processus de se chevaucher et d'actualiser le même mot clé, ainsi que la collecte de statistiques. Donc, quand je lance mon programme, il interroge tous les mots-clés qui n'ont pas de demande en cours, et qui n'ont pas réussi dans les 15 dernières minutes, ou quel que soit l'intervalle. Tout fonctionnait bien pendant un certain temps, mais maintenant la table keywords_requests a près de 2 millions de lignes et les choses s'enlisent mal. J'ai des index sur presque chaque colonne dans la table keywords_requests, mais en vain.Comment optimiser la recherche de lignes où les lignes de jointures conditionnelles n'existent pas?

Je suis en train d'enregistrer des requêtes lentes et celle-ci prend une éternité, comme vous pouvez le voir. Que puis-je faire?

 
# Query_time: 20 Lock_time: 0 Rows_sent: 568 Rows_examined: 1826718 

SELECT Keyword.id, Keyword.keyword 
FROM `keywords` as Keyword 
LEFT JOIN `keywords_requests` as KeywordsRequest 
ON (
    KeywordsRequest.keyword_id = Keyword.id 
    AND (KeywordsRequest.status = 'success' OR KeywordsRequest.status = 'active') 
    AND KeywordsRequest.source_id = '29' 
    AND KeywordsRequest.created > FROM_UNIXTIME(1234551323) 
) 
WHERE KeywordsRequest.id IS NULL 
GROUP BY Keyword.id 
ORDER BY KeywordsRequest.created ASC; 

Répondre

0

J'sais sur MySQL, mais dans MSSQL les lignes d'attaque, je prendrais sont:

1) Créer un index de couverture sur l'état KeywordsRequest, source_id et créé

2) UNION les résultats TOG et autour du OU sur KeywordsRequest.status

3) Utilisez NOT EXISTS au lieu o la jointure externe (et essayer avec UNION au lieu de ou trop)

1

Whe n diagnostiquant les performances des requêtes MySQL, l'une des premières choses que vous devez analyser est le rapport de EXPLAIN. Si vous apprenez à lire les informations que EXPLAIN vous donne, vous pouvez voir où les requêtes ne parviennent pas à utiliser les index, ou où elles causent des filesors coûteux, ou d'autres indicateurs de performance.

Je remarque dans votre requête que le GROUP BY n'est pas pertinent, car il n'y aura qu'une seule ligne NULL renvoyée par KeywordRequests. En outre, le ORDER BY est sans importance, puisque vous commandez par une colonne qui sera toujours NULL en raison de votre clause WHERE. Si vous supprimez ces clauses, vous éliminerez probablement un fichier.

Envisagez également de réécrire la requête dans d'autres formulaires et de mesurer les performances de chacun. Par exemple:

SELECT k.id, k.keyword 
FROM `keywords` AS k 
WHERE NOT EXISTS (
    SELECT * FROM `keywords_requests` AS kr 
    WHERE kr.keyword_id = k.id 
    AND kr.status IN ('success', 'active') 
    AND kr.source_id = '29' 
    AND kr.created > FROM_UNIXTIME(1234551323) 
); 

Autres conseils:

  • Est-kr.source_id un entier? Si oui, comparez à l'entier 29 au lieu de la chaîne '29'.
  • Y a-t-il des index appropriés sur keyword_id, status, source_id, created? Peut-être même un index composé sur les quatre colonnes serait le meilleur, puisque MySQL utilisera un seul index par table dans une requête donnée.

vous avez fait une capture d'écran de votre EXPLAIN et a posté un lien dans les commentaires. Je vois que la requête est pas en utilisant un index de mots-clés, ce qui est logique puisque vous analysez toutes les lignes de cette table de toute façon. La phrase "Not exists" indique que MySQL a optimisé un peu le LEFT OUTER JOIN.

Je pense que cela devrait être amélioré par rapport à votre requête d'origine.Le GROUP BY/ORDER BY l'a probablement amené à enregistrer un ensemble de données intermédiaire en tant que table temporaire, et à le trier sur le disque (ce qui est très lent!). Ce que vous recherchez est "Utilisation temporaire, using filesort" dans la colonne Extra d'informations EXPLAIN.

Vous l'avez peut-être déjà suffisamment amélioré pour réduire le goulot d'étranglement pour l'instant.

Je remarque que les clés possibles indiquent probablement que vous avez des index individuels sur quatre colonnes. Vous pouvez être en mesure d'améliorer la situation en créant un indice composé:

CREATE INDEX kr_cover ON keywords_requests 
    (keyword_id, created, source_id, status); 

Vous pouvez donner MySQL un hint d'utiliser un index spécifique:

... FROM `keywords_requests` AS kr USE INDEX (kr_cover) WHERE ... 
+0

Wow, merci pour votre aide. Le EXPLAIN pour cette requête n'a pas été très utile pour moi. Voici ce que j'ai obtenu après avoir supprimé les clauses GROUP BY et ORDER BY ... qu'est-ce que je cherche? http://img.skitch.com/20090213-fq5mkucasgdqc3ck9aeca6qr9p.jpg –

+0

En outre, j'ai obtenu le plus d'amélioration de la vitesse en utilisant l'exemple de Quassnoi ci-dessous. Mais si je voulais accélérer les choses, un index multi-colonnes pourrait-il encore m'aider?Aurais-je juste besoin de source_id, status et keyword_id dans l'index, puisque created est dans une sous-requête? –

+0

Difficile à deviner, car le choix des index de l'optimiseur dépend en partie de la distribution des valeurs de données dans votre base de données. Le meilleur conseil est de l'essayer de différentes façons et de mesurer à la fois les informations EXPLAIN ainsi que le temps réel pour exécuter la requête. –

2

Il semble que votre index le plus sélectif sur Keywords est l'une sur KeywordRequest.created.

Essayez de réécrire la requête de cette façon:

SELECT Keyword.id, Keyword.keyword 
FROM `keywords` as Keyword 
LEFT OUTER JOIN (
    SELECT * 
    FROM `keywords_requests` as kr 
    WHERE created > FROM_UNIXTIME(1234567890) /* Happy unix_time! */ 
) AS KeywordsRequest 
ON (
    KeywordsRequest.keyword_id = Keyword.id 
    AND (KeywordsRequest.status = 'success' OR KeywordsRequest.status = 'active') 
    AND KeywordsRequest.source_id = '29' 
) 
WHERE keyword_id IS NULL; 

Il (je l'espère) hachage joindre deux pas de grandes sources.

Et Bill Karwin est juste, vous n'avez pas besoin GROUP BY ou ORDER BY

Il n'y a pas un contrôle précis sur les plans de MySQL, mais vous pouvez essayer (essayer) pour améliorer votre requête de la manière suivante:

  1. Créer un index composite sur (keyword_id, status, source_id, created) et qu'il en soit ainsi:

    SELECT Keyword.id, Keyword.keyword 
    FROM `keywords` as Keyword 
    LEFT OUTER JOIN `keywords_requests` kr 
    ON (
        keyword_id = id 
        AND status = 'success' 
        AND source_id = '29' 
        AND created > FROM_UNIXTIME(1234567890) 
    ) 
    WHERE keyword_id IS NULL 
    UNION 
    SELECT Keyword.id, Keyword.keyword 
    FROM `keywords` as Keyword 
    LEFT OUTER JOIN `keywords_requests` kr 
    ON (
        keyword_id = id 
        AND status = 'active' 
        AND source_id = '29' 
        AND created > FROM_UNIXTIME(1234567890) 
    ) 
    WHERE keyword_id IS NULL 
    

    Cela devrait idéalement utiliser NESTED LOOPS sur votre index.

  2. Créer un index composite sur (status, source_id, created) et qu'il en soit ainsi:

    SELECT Keyword.id, Keyword.keyword 
    FROM `keywords` as Keyword 
    LEFT OUTER JOIN (
        SELECT * 
        FROM `keywords_requests` kr 
        WHERE 
        status = 'success' 
        AND source_id = '29' 
        AND created > FROM_UNIXTIME(1234567890) 
        UNION ALL 
        SELECT * 
        FROM `keywords_requests` kr 
        WHERE 
        status = 'active' 
        AND source_id = '29' 
        AND created > FROM_UNIXTIME(1234567890) 
    ) 
    ON keyword_id = id 
    WHERE keyword_id IS NULL 
    

    Cela devrait utiliser HASH JOIN sur la table de hachage encore plus restreint.

+0

Wow, cela réduit le temps de requête à rien dans quelques cas, et moins d'une seconde dans le cas commun. Très agréable. Une question subséquente: le temps de requête s'allonge au fur et à mesure que je regarde, alors pourrais-je optimiser davantage avec un index multi-colonnes? –

0

Essayez cette SELECT Keyword.id, Keyword.keyword DE keywords comme les mots-clés LEFT JOIN (select * from keywords_requests où source_id = '29' et (status = 'réussite' OU status = 'actif') ET source_id = '29' ET créé> FROM_UNIXTIME (1234551323) ET id est NULL) comme KeywordsRequest ON ( KeywordsRequest.keyword_id = Keyword.id

) GROUP BY Ke yword.id ORDER BY KeywordsRequest.created ASC;

Questions connexes