2010-11-01 7 views
0

J'essaie d'optimiser une requête qui prend trop de temps à s'exécuter telle qu'elle est. Il semble être coincé dans l'envoi de données et prend environ une demi-heure pour fonctionner.Optimisation d'une grande requête MySQL

 

$campaignIDs = "31,36,37,40,41,42,43,50,51,62,64,65,66,67,68,69,84,338,339,355,431,505,530,549,563,694,752,754,755,760,769,772,777,798,799,800,806,816,821,855,856,945,989,1007,1030,1032,1047,1052,1054,1066,1182,1268,1281,1298,1301,1317,1348,1447,1461,1471,1589,1602,1604,1615,1622,1650,1652,1709"; 

SELECT Email, Type, CampaignID 
FROM Refer 
WHERE (Type = 'V' OR Type = 'C') 
    AND (EmailDomain = 'yahoo.com') 
    AND (ListID = 1) 
    AND CampaignID IN ($campaignIDs) 
    AND Date >= DATE_SUB(NOW(), INTERVAL 90 DAY) 

Voici ce que la page Parrainer tableau ressemble:

 
+-------------+------------------+------+-----+-------------------+----------------+ 
| Field  | Type    | Null | Key | Default   | Extra   | 
+-------------+------------------+------+-----+-------------------+----------------+ 
| ID   | int(10) unsigned | NO | PRI | NULL    | auto_increment | 
| CampaignID | int(10) unsigned | NO | MUL | NULL    |    | 
| Type  | char(1)   | NO | MUL | NULL    |    | 
| Date  | timestamp  | NO |  | CURRENT_TIMESTAMP |    | 
| IP   | varchar(16)  | NO |  | NULL    |    | 
| Useragent | varchar(200)  | YES |  | NULL    |    | 
| Referrer | varchar(200)  | YES |  | NULL    |    | 
| Email  | varchar(200)  | NO | MUL | NULL    |    | 
| EmailDomain | varchar(200)  | YES | MUL | NULL    |    | 
| FolderName | varchar(200)  | NO |  | NULL    |    | 
| ListID  | int(10) unsigned | NO | MUL | 1     |    | 
+-------------+------------------+------+-----+-------------------+----------------+ 

Voici les indices:

 
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 
| Table | Non_unique | Key_name  | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | 
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 
| refer |   0 | PRIMARY  |   1 | ID   | A   | 148581841 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_email  |   1 | Email  | A   | 18572730 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_type  |   1 | Type  | A   |   19 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_emaildomain |   1 | EmailDomain | A   |   19 |  NULL | NULL | YES | BTREE  |   | 
| refer |   1 | id_campaignid |   1 | CampaignID | A   |   19 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_listid  |   1 | ListID  | A   |   19 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_emailtype |   1 | Email  | A   | 24763640 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_emailtype |   2 | Type  | A   | 37145460 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | idx_cidtype |   1 | CampaignID | A   |   19 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | idx_cidtype |   2 | Type  | A   |   19 |  NULL | NULL |  | BTREE  |   | 
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 

est ici la sortie Explain SELECT:

 
+----+-------------+-------+-------+------------------------------------------------------------+---------------+---------+------+---------+-------------+ 
| id | select_type | table | type | possible_keys            | key   | key_len | ref | rows | Extra  | 
+----+-------------+-------+-------+------------------------------------------------------------+---------------+---------+------+---------+-------------+ 
| 1 | SIMPLE  | Refer | range | id_type,id_emaildomain,id_campaignid,id_listid,idx_cidtype | id_campaignid | 4  | NULL | 3605121 | Using where | 
+----+-------------+-------+-------+------------------------------------------------------------+---------------+---------+------+---------+-------------+ 

Il y a environ 150M lignes dans la table.

Y a-t-il quelque chose que je peux faire pour optimiser la requête en question? Ai-je besoin d'ajouter des index ou quelque chose? Comment puis-je améliorer les choses?

Répondre

2

Vous pouvez essayer l'index suivant pour régler cette déclaration

ALTER TABLE refer 
    ADD INDEX so_suggested (EmailDomain, ListID, Date); 

Ceci est juste ma première pensée. Vous pouvez également ajouter CampaignID et Type pour le rendre plus efficace - s'ils sont sélectifs. Si vous ajoutez les deux, vous pouvez même essayer d'ajouter Email pour en faire un covering index.

Cependant, le nombre d'index sur cette table est plutôt élevé (huit). Deux d'entre eux sont redondants (id_email, id_campaignid) car il y en a d'autres qui commencent par la même colonne (id_emailtype, idx_cidtype).

Veuillez noter que (en principe) un accès à la table utilise un seul index. Votre requête n'a qu'un seul accès à la table (pas de sous-requêtes, de jointures, UNION ou plus), donc elle ne peut utiliser qu'un seul index. Par conséquent, vous avez besoin un index qui prend en charge autant que possible de votre clause where.

Veuillez noter également que l'ordre des colonnes dans cet index est très important. J'ai ajouté ceux qui correspondent exactement en premier (EmailDomain, ListID), suivi par celui qui utilise un opérateur en égalité (Date) - en supposant que la clause un Date est encore assez sélectif. Tout ce qui suit l'opération in-equality est juste un filtre dans l'index - si nécessaire, vous pouvez ajouter les listes IN ici.

Annonce

Juste au cas où vous souhaitez en savoir plus sur l'indexation de base de données: Jetez un oeil à mon free eBook on database indexing.

2

Il y a peu de place ici pour accorder la requête, mais vous pouvez probablement la faire aller beaucoup plus vite en ajustant le schéma de base de données - l'astuce consiste à identifier un index potentiel aussi spécifique que possible.

par exemple.

ET Date> = DATE_SUB (NOW(), INTERVALLE 90 JOUR)

suggère qu'un index sur 'date' pourrait aider - mais seulement si vos données sont bien réparties sur au moins 4 ans .

En pratique et particulièrement lorsque vous ne devez cibler que des requêtes spécifiques, les index composés sont une bonne idée - mais le meilleur choix d'index dépend non seulement de la taille et de la forme de vos données mais également des autres requêtes. votre base de données.

En regardant votre requête:

WHERE (Type = 'V' OR Type = 'C') 
    AND (EmailDomain = 'yahoo.com') 
    AND (ListID = 1) 
    AND CampaignID IN ($campaignIDs) 
    AND Date >= DATE_SUB(NOW(), INTERVAL 90 DAY) 

Vous pouvez simplement ajouter un index sur (type, domainecourrier, ListId, CampaignId et date) mais je soupçonne que CampaignId et la date ont le plus grand cardinalité et il convient donc à le recto de l'index - l'index doit être ordonné sur le rapport de la cardinalité dans l'ensemble de données en entrée (la table) à la sortie de la requête. par exemple. si vous régulièrement une requête exécuté:

AND Date >= DATE_SUB(NOW(), INTERVAL 90000 DAY) 

alors vous n'allez obtenir autant d'avantages d'avoir date à l'avant de l'indice. De même, il semble que Type a un ensemble de valeurs très limité et devrait apparaître plus tard dans l'index que CampaignId (en supposant que vous ne regardiez qu'un nombre relativement petit de CampaignIds à tout moment).

Pour obtenir une estimation de la cardinalité, tenez compte:

SELECT COUNT(records_of_type)/SUM(records_of_type) 
FROM (SELECT afield, COUNT(*) AS records_of_type 
    FROM atable) 

(valeurs élevées sont plus sélectives et doivent normalement apparaître à l'avant d'un indice). Mais n'oubliez pas que vous verrez occasionnellement des dépendances fonctionnelles entre les colonnes. La commande de votre ordre de champ d'index par cardinality ne diminue pas le nombre de nœuds d'index que le SGBD doit visiter pour satisfaire la requête, mais devrait entraîner une diminution du nombre d'opérations d'E/S de disque nécessaires.

Cependant, il est beaucoup plus important d'identifier les champs qui apparaissent dans les index avant de s'inquiéter de la commande.

0

Pourrait essayer quelques approches différentes pour cela.

Une chose que vous pouvez essayer:

$date = mysql_query("SELECT DATE_SUB(NOW(), INTERVAL 90 DAY) AS date"); 

SELECT * FROM (
    SELECT Email, Type, CampaignID 
    FROM Refer 
    WHERE (Type = 'V' OR Type = 'C') 
    AND (EmailDomain = 'yahoo.com') 
    AND (ListID = 1) 
) 
    WHERE Date >= $date 
    AND CampaignID IN ($campaignIDs) 

Index cette requête sur (Type domainecourrier ListID) et vous devriez voir un gain significatif de performance. Vous pouvez également jouer avec l'ordre de l'index (mais assurez-vous que la requête correspond). Le but de ceci est de prendre la partie rapide de votre requête, et l'exécuter par rapport au plus grand nombre d'enregistrements, puis de prendre la partie lente de votre requête et de l'exécuter sur ce jeu beaucoup plus petit.

Vous devrez peut-être créer une table temporaire pour que sql le fasse; Cependant, je n'avais pas à le faire pour mon test. Notez également que j'ai retiré l'appel de la fonction de la grosse requête lente et l'ai transformée en une constante.