2009-09-05 8 views
1

Je suis en cours d'exécution de la requête suivante sur un Macbook Pro 2,53 avec 4 Go de RAM:Optimisation de la requête MySQL, prend presque 20 secondes!

SELECT 
    c.id   AS id, 
    c.name   AS name, 
    c.parent_id  AS parent_id, 
    s.domain  AS domain_name, 
    s.domain_id  AS domain_id, 
    NULL   AS stats 
FROM 
    stats s 
LEFT JOIN stats_id_category sic ON s.id = sic.stats_id 
LEFT JOIN categories c ON c.id = sic.category_id 
GROUP BY 
    c.name 

Il faut environ 17 secondes pour terminer.

EXPLIQUEZ:

alt text http://img7.imageshack.us/img7/1364/picture1va.png

Les tables:

Information:

Number of rows: 147397 
Data size: 20.3MB 
Index size: 1.4MB 

Tableau:

CREATE TABLE `stats` (
    `id` int(11) unsigned NOT NULL auto_increment, 
    `time` int(11) NOT NULL, 
    `domain` varchar(40) NOT NULL, 
    `ip` varchar(20) NOT NULL, 
    `user_agent` varchar(255) NOT NULL, 
    `domain_id` int(11) NOT NULL, 
    `date` timestamp NOT NULL default CURRENT_TIMESTAMP, 
    `referrer` varchar(400) default NULL, 
    KEY `id` (`id`) 
) ENGINE=MyISAM AUTO_INCREMENT=147398 DEFAULT CHARSET=utf8 

Informations seco Table e:

Number of rows: 1285093 
Data size: 11MB 
Index size: 17.5MB 

Deuxième tableau:

CREATE TABLE `stats_id_category` (
    `stats_id` int(11) NOT NULL, 
    `category_id` int(11) NOT NULL, 
    KEY `stats_id` (`stats_id`,`category_id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8 

Informations troisième table:

Number of rows: 161 
Data size: 3.9KB 
Index size: 8KB 

Troisième table:

CREATE TABLE `categories` (
    `id` int(11) NOT NULL auto_increment, 
    `parent_id` int(11) default NULL, 
    `name` varchar(40) NOT NULL, 
    `questions_category_id` int(11) NOT NULL default '0', 
    `rank` int(2) NOT NULL default '0', 
    PRIMARY KEY (`id`),  
    KEY `id` (`id`) 
) ENGINE=MyISAM AUTO_INCREMENT=205 DEFAULT CHARSET=latin1 

Espérons que quelqu'un peut me aider à accélérer cela.

+0

Quelles sont les spécifications de votre serveur? 17 sec peut ne pas être trop mauvais pour joindre 100K rangées avec des rangées de 1M. – dave

+1

Veuillez inclure la sortie d'EXPLAIN. – derobert

+0

vous pouvez utiliser limite et traiter votre sortie par lots. Quel genre de traitement faites-vous avec tellement d'enregistrements? –

Répondre

0

Il vous manque le troisième tableau dans les informations fournies (catégories).

Aussi, il semble étrange que vous faites un LEFT JOIN et ensuite en utilisant la bonne table (qui pourrait être tous NULLS) dans le GROUP BY. Vous finirez par regrouper toutes les lignes qui ne correspondent pas, c'est ce que vous avez prévu?

Enfin, pouvez-vous fournir un EXPLAIN pour le SELECT?

+0

J'ai ajouté la troisième table, mes informations système et un Explain. Merci! –

0

Harrison a raison; nous avons besoin de l'autre table. Je commencerais par ajouter un index sur category_id à stats_id_category, cependant.

+0

Il existe déjà un INDEX sur category_id et stats_id (ensemble). Ou serait-il préférable de les séparer? –

+0

Soit vous effectuez une recherche avec la clé d'un index, soit vous ne le faites pas. Si vous avez une clé composée, à moins que vous ne cherchiez ces deux champs, l'index ne sera pas utilisé. –

3

Je vois plusieurs années WTF dans votre requête:

  1. Vous utilisez deux LEFT OUTER JOIN s mais vous groupez par la colonne c.name qui pourrait avoir aucune correspondance. Alors peut-être n'avez-vous pas vraiment besoin d'une jointure externe? Si c'est le cas, vous devez utiliser une jointure interne, car les jointures externes sont souvent plus lentes.

  2. Vous regroupez par c.name mais cela donne des résultats ambigus pour toutes les autres colonnes de votre liste de sélection. C'est à dire. il peut y avoir plusieurs valeurs dans ces colonnes dans chaque groupe par c.name. Vous avez de la chance que vous utilisiez MySQL, car cette requête donnerait simplement une erreur dans tout autre SGBDR.

    Il s'agit d'un problème de performance car le GROUP BY est probablement à l'origine du "using temporary; using filesort" que vous voyez dans EXPLAIN. C'est un tueur de performance notoire, et c'est probablement la principale raison pour laquelle cette requête prend 17 secondes.Comme vous ne savez pas du tout pourquoi vous utilisez GROUP BY (n'utilisez pas de fonctions d'agrégation et ne respectez pas la règle de valeur unique), il semble que vous deviez repenser cela.

  3. Vous regroupez par c.name qui n'a pas de contrainte UNIQUE dessus. Vous pourriez en théorie avoir plusieurs catégories avec le même nom, et celles-ci seraient regroupées dans un groupe. Je me demande pourquoi vous ne regroupez pas par c.id si vous voulez un groupe par catégorie.

  4. SELECT NULL AS stats: Je ne comprends pas pourquoi vous en avez besoin. C'est un peu comme créer une variable que vous n'utilisez jamais. Cela ne devrait pas nuire à la performance, mais c'est juste un autre WTF qui me fait penser que vous n'avez pas très bien réfléchi à cette requête.

  5. Vous dites dans un commentaire que vous cherchez le nombre de visiteurs par catégorie. Mais votre requête n'a aucune fonction d'agrégat comme SUM() ou COUNT(). Et votre liste de sélection comprend s.domain et s.domain_id ce qui serait différent pour chaque visiteur, non? Alors, quelle valeur attendez-vous dans le jeu de résultats si vous n'avez qu'une seule ligne par catégorie? Ce n'est pas vraiment un problème de performance non plus, cela signifie simplement que les résultats de vos requêtes ne vous disent rien d'utile.

  6. Votre table stats_id_category a un index sur ses deux colonnes, mais pas de clé primaire. Ainsi, vous pouvez facilement obtenir des lignes en double, ce qui signifie que votre nombre de visiteurs peut être inexact. Vous devez supprimer cet index redondant et utiliser une clé primaire à la place. Je commande d'abord category_id dans cette clé primaire, de sorte que la jointure peut tirer parti de l'index.

    ALTER TABLE stats_id_category DROP KEY stats_id, 
        ADD PRIMARY KEY (category_id, stats_id); 
    

Maintenant vous pouvez éliminer l'un de vos jointures, si vous avez besoin de compter est le nombre de visiteurs:

SELECT c.id, c.name, c.parent_id, COUNT(*) AS num_visitors 
FROM categories c 
INNER JOIN stats_id_category sic ON (sic.category_id = c.id) 
GROUP BY c.id; 

Maintenant, la requête n'a pas besoin de lire la table stats du tout, ou même le stats_id_category table. Il peut obtenir son compte simplement en lisant l'index de la table stats_id_category, ce qui devrait éliminer beaucoup de travail.

0

Je suis d'accord avec Bill. Le point 2 est très important. La requête n'a même pas de sens logique. De plus, avec le simple fait qu'il n'y a pas d'instruction where, vous devez retirer chaque ligne de la table de statistiques, qui semble se situer autour de 140000. Elle doit ensuite trier toutes ces données, afin qu'elle puisse effectuer le GROUP BY . C'est parce que trier [O (n log n)] et ensuite trouver des doublons [O (n)] est beaucoup plus rapide que de simplement trouver des doublons sans trier l'ensemble de données [O (n^2) ?? ]

Questions connexes