3

J'ai les deux tables suivantes dans MySQL (simplifié).Requête MySQL avec JOIN n'utilisant pas INDEX

  • clicks (InnoDB)
    • contient environ environ 70.000.000 records
    • a un index sur la colonne date_added
    • A une colonne link_id qui fait référence à un enregistrement de la table links
  • links (MyISAM)
      beaucoup moins
    • Contient des dossiers, autour d'environ 65.000

Je suis en train d'exécuter des requêtes analytiques en utilisant ces tables. Je dois extraire des données, à propos des clics survenus à l'intérieur de deux dates spécifiées, tout en appliquant d'autres filtres sélectionnés par d'autres utilisateurs en utilisant d'autres tables et en les joignant à la table des liens.

Ma question tourne cependant autour de l'utilisation des index. Lorsque je cours la requête suivante:

SELECT 
    COUNT(1) 
FROM 
    clicks 
WHERE 
    date_added >= '2016-11-01 00:00:00' 
AND date_added <= '2016-11-03 23:59:59'; 

Je reçois une réponse en 1.40 sec. En utilisant EXPLAIN, je trouve que MySQL utilise l'index sur la colonne date_added comme prévu.

EXPLAIN SELECT COUNT(1) FROM clicks WHERE date_added >= '2016-11-01 00:00:00' AND date_added <= '2016-11-16 23:59:59'; 
+----+-------------+--------+-------+---------------+------------+---------+------+---------+--------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref | rows | Extra     | 
+----+-------------+--------+-------+---------------+------------+---------+------+---------+--------------------------+ 
| 1 | SIMPLE  | clicks | range | date_added | date_added | 4  | NULL | 1559288 | Using where; Using index | 
+----+-------------+--------+-------+---------------+------------+---------+------+---------+--------------------------+ 

Cependant, quand je LEFT JOIN dans mon tableau links je trouve que la requête prend beaucoup plus de temps à exécuter:

SELECT 
    COUNT(1) AS clicks 
FROM 
    clicks AS c 
LEFT JOIN links AS l ON l.id = c.link_id 
WHERE 
    c.date_added >= '2016-11-01 00:00:00' 
AND c.date_added <= '2016-11-16 23:59:59'; 

qui a terminé à 6,50 secondes. En utilisant EXPLAIN Je trouve que l'indice n'a pas été utilisé sur la date_added colonne:

EXPLAIN SELECT COUNT(1) AS clicks FROM clicks AS c LEFT JOIN links AS l ON l.id = c.link_id WHERE c.date_added >= '2016-11-01 00:00:00' AND c.date_added <= '2016-11-16 23:59:59'; 
+----+-------------+-------+--------+---------------+------------+---------+---------------+---------+-------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra  | 
+----+-------------+-------+--------+---------------+------------+---------+---------------+---------+-------------+ 
| 1 | SIMPLE  | c  | range | date_added | date_added | 4  | NULL   | 6613278 | Using where | 
| 1 | SIMPLE  | l  | eq_ref | PRIMARY  | PRIMARY | 4  | c.link_id  |  1 | Using index | 
+----+-------------+-------+--------+---------------+------------+---------+---------------+---------+-------------+ 

Comme vous pouvez le voir l'index n'est pas utilisé pour la colonne date_added dans la plus grande table et semble prendre beaucoup plus longtemps. Cela semble devenir encore pire quand je me joins à d'autres tables.

Est-ce que quelqu'un sait pourquoi cela se produit ou s'il y a quelque chose que je peux faire pour l'utiliser pour utiliser l'index sur la colonne date_added dans le tableau des clics?


Modifier

Je viens juste essayé d'obtenir mes statistiques de la base de données en utilisant une autre méthode. La première étape de ma méthode consiste à extraire un ensemble distinct de link_id depuis la table des clics. J'ai constaté que je vois le même problème ici encore, sans JOIN. L'indice n'est pas utilisé:

Ma requête:

SELECT 
    DISTINCT(link_id) AS link_id 
FROM 
    clicks 
WHERE 
    date_added >= '2016-11-01 00:00:00' 
AND date_added <= '2016-12-05 10:16:00' 

Cette requête a pris près d'une minute.J'ai couru un EXPLAIN sur ce sujet et trouvé que la requête n'utilise l'index comme je m'y attendais serait:

+----+-------------+---------+-------+---------------+----------+---------+------+----------+-------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref | rows  | Extra  | 
+----+-------------+---------+-------+---------------+----------+---------+------+----------+-------------+ 
| 1 | SIMPLE  | clicks | index | date_added | link_id | 4  | NULL | 79786609 | Using where | 
+----+-------------+---------+-------+---------------+----------+---------+------+----------+-------------+ 

Je pensais qu'il utiliserait l'index sur date_added pour filtrer sur le jeu de résultats, puis retirez la valeurs link_id distinctes. Une idée de pourquoi cela se passe? J'ai un index sur link_id ainsi que date_added.

+0

Veuillez ajouter ** index ** sur link_id dans les tables de clics qui peuvent aider à réduire le temps de sortie de la requête –

+0

@SumanEStatic - 'INDEX (link_id)' ne serait pas utile. –

+0

On dirait que vous utilisez MyISAM. S'il vous plaît fournir 'SHOW CREATE TABLE'. –

Répondre

1

pas absolument sûr, mais envisager de transférer la condition de WHERE condition JOIN ON état puisque vous effectuez une jointure externe (LEFT JOIN), il fait la différence dans la performance à la différence inner join où la condition que ce soit sur where ou join on clause est équivalente.

SELECT COUNT(1) AS clicks 
FROM clicks AS c 
LEFT JOIN links AS l ON l.id = c.link_id 
AND (c.date_added >= '2016-11-01 00:00:00' 
AND c.date_added <= '2016-11-16 23:59:59'); 
+0

Merci pour votre réponse. J'ai essayé de déplacer les conditions de la clause WHERE vers le JOIN ON comme suggéré mais je vois toujours le même problème. – Jonathon

+0

Utilisez 'ON' pour dire comment les tables sont liées; utilisez 'WHERE' pour le filtrage. L'Optimizer _peut_ les traiter de manière identique. Vous pouvez voir cela depuis 'EXPLAIN EXTENDED SELECT ...; SHOW WARNINGS; ' –

+0

@Rahul J'ai édité ma question avec quelque chose d'autre que j'ai trouvé qui pourrait potentiellement aider à diagnostiquer le problème. Merci :) – Jonathon

1

Voulez-vous utiliser un JOIN ordinaire à la place du LEFT JOIN? LEFT JOIN préserve toutes les lignes sur la droite, donc il donnera la même valeur de COUNT() que la table non jointe. Si vous voulez compter uniquement les lignes de votre table de droite qui ont des lignes correspondantes dans la table de gauche, utilisez JOIN, et non LEFT JOIN. Essayez de supprimer votre index sur date_added et de le remplacer par un index composé sur (date_added, link_id). This sort of index is called a covering inde x. Lorsque le planificateur de requêtes sait qu'il peut obtenir tout ce dont il a besoin à partir d'un index, il n'a pas besoin de rebondir sur la table. Dans ce cas, le planificateur de requêtes peut accéder de manière aléatoire à l'index au début de votre plage de dates, puis effectuer un index range scan jusqu'à la fin de la plage. Cela va quand même devoir se référer à l'autre table.

(Modifier) Pour des raisons d'expérimentation, essayez une plage de dates plus étroite. Voyez si EXPLAIN changements. Dans ce cas, le planificateur de requêtes peut être en train de deviner que la cardinalité de la colonne date_added est incorrecte. Vous pouvez essayer un index hint. Par exemple, essayez

SELECT COUNT(1) AS clicks 
    FROM clicks AS c USE INDEX (date_added) 
    LEFT JOIN links AS l ON l.id = c.link_id 
WHERE etc 

Mais, à en juger de votre sortie EXPLAIN, vous faites déjà une analyse de gamme sur date_added. Votre prochaine étape, que cela vous plaise ou non, est l'indice de couverture composé.

Assurez-vous qu'il existe un index sur links(id). Il y en a probablement, car c'est probablement le PK. Utilisez le COUNT(*) au lieu de COUNT(1). Cela ne fera probablement pas de différence, mais ça vaut le coup d'essayer. COUNT(*) compte simplement les lignes plutôt que d'évaluer quelque chose pour chaque ligne qu'il compte.

(Nitpick) Votre plage de dates sent drôle. Utilisez < pour la fin de votre gamme pour de meilleurs résultats, comme ça.

WHERE c.date_added >= '2016-11-01' 
    AND c.date_added < '2016-11-17'; 

Modifier: Regardez, le planificateur de requêtes MySQL utilise beaucoup de connaissances internes sur la façon dont les tables sont structurées. Et, il peut seulement utiliser un index par table pour satisfaire une requête à la fin de 2016. C'est une limitation.

SELECT DISTINCT column est en fait une requête assez complexe, car elle doit dé-dupe le column en question. S'il existe un index sur cette colonne, le planificateur de requêtes est susceptible de l'utiliser. Choisir cet index signifie qu'il ne peut pas choisir un autre index.

Les index composés (couvrant les index) parfois mais pas toujours résolvent ce type de dilemme de sélection d'index et autorisent l'utilisation double index. Vous pouvez lire à propos de tout ceci à http://use-the-index-luke.com/

Mais si vos contraintes opérationnelles empêchent l'ajout d'index composés, vous devrez vivre avec la requête d'une seconde. Ce n'est pas si mal.

Bien sûr, dire que vous ne pouvez pas ajouter des index composés pour faire votre travail est comme ceci:

A: Des vêtements tombe de mon camion sur l'autoroute.

B: mettre une bâche sur la substance et l'attacher.

A: mon patron ne me laisse pas mettre une bâche sur le camion.

B: bien, alors, ralentissez.

+0

Merci pour votre réponse. J'ai essayé d'utiliser un "JOIN" au lieu de "LEFT JOIN" sans succès, j'ai aussi essayé COUNT (*) 'en vain. Je peux confirmer que 'links (id)' est une clé primaire et que la colonne 'clicks (link_id)' est également indexée. Je suis réticent en ce moment à apporter des modifications à la table en raison de sa taille, donc je n'ai pas réussi à supprimer l'index et à rajouter un index de couverture comme suggéré. Merci encore! – Jonathon

+0

Vous pouvez ajouter le nouvel index sans supprimer le premier existant. –

+0

En utilisant 'ALTER TABLE', vous pouvez ajouter et supprimer n'importe quel nombre d'index simultanément. –