MySQL et non pas à l'aide de l'index avec une JOINTURE, where et ORDER
Nous avons deux tables qui ressemble à un simple tag-la structure de l'enregistrement comme suit (en réalité c'est beaucoup plus complexe, mais c'est l'essence même du problème):
tag (A.a) | recordId (A.b)
1 | 1
2 | 1
2 | 2
3 | 2
....
et
recordId (B.b) | recordData (B.c)
1 | 123
2 | 666
3 | 1246
Le problème est l'extraction commandé des records avec une étiquette spécifique. Le moyen le plus évident de le faire est avec une simple jointure et l'index sur (PK)(A. a, A. b), (A. b), (PK)(B. b), (b,B. c) en tant que tel:
select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 order by c;
Cependant, cela donne la désagréable résultat d'un filesort:
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+
| 1 | SIMPLE | A | ref | PRIMARY,b | PRIMARY | 4 | const | 94 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | B | ref | PRIMARY,b | b | 4 | booli.A.b | 1 | Using index |
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+
À l'aide d'une énorme et extrêmement redondant "de la vue matérialisée" nous pouvons obtenir assez décent de performance, mais cela au détriment de compliquer les affaires de la logique, quelque chose que nous aimerions éviter, surtout depuis le A et le B tables sont déjà MV:s (et sont nécessaires pour les autres requêtes, et en fait les mêmes requêtes à l'aide d'un SYNDICAT).
create temporary table C engine=innodb as (select A.a, A.b, B.c from A join B on A.b = B.b);
explain select a, b, c from C where a = 44 order by c;
À compliquer davantage la situation est le fait que nous avons des conditions sur le B-table, tels que des filtres.
select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 AND B.c > 678 order by c;
Mais nous sommes confiants que nous pouvons gérer cela si le filesort problème disparaît.
Personne ne sait pourquoi le simple jointure dans codeblock 3 ci-dessus ne pas utiliser l'index pour le tri et si l'on peut contourner le problème en quelque sorte, sans la création d'un nouveau MV?
Ci-dessous la liste complète de SQL que l'on utilise pour les tests.
DROP TABLE IF EXISTS A;
DROP TABLE IF EXISTS B;
DROP TABLE IF EXISTS C;
CREATE TEMPORARY TABLE A (a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a, b), INDEX idx_A_b (b)) ENGINE=INNODB;
CREATE TEMPORARY TABLE B (b INT NOT NULL, c INT NOT NULL, d VARCHAR(5000) NOT NULL DEFAULT '', PRIMARY KEY(b), INDEX idx_B_c (c), INDEX idx_B_b (b, c)) ENGINE=INNODB;
DELIMITER $$
CREATE PROCEDURE prc_filler(cnt INT)
BEGIN
DECLARE _cnt INT;
SET _cnt = 1;
WHILE _cnt <= cnt DO
INSERT IGNORE INTO A SELECT RAND()*100, RAND()*10000;
INSERT IGNORE INTO B SELECT RAND()*10000, RAND()*1000, '';
SET _cnt = _cnt + 1;
END WHILE;
END
$$
DELIMITER ;
START TRANSACTION;
CALL prc_filler(100000);
COMMIT;
DROP PROCEDURE prc_filler;
CREATE TEMPORARY TABLE C ENGINE=INNODB AS (SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b);
ALTER TABLE C ADD (PRIMARY KEY(a, b), INDEX idx_C_a_c (a, c));
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE A.a = 44;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE 1 ORDER BY B.c;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b where A.a = 44 ORDER BY B.c;
EXPLAIN EXTENDED SELECT a, b, c FROM C WHERE a = 44 ORDER BY c;
-- Added after Quassnois comments
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM B FORCE INDEX (idx_B_c) JOIN A ON A.b = B.b WHERE A.a = 44 ORDER BY B.c;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE A.a = 44 ORDER BY B.c LIMIT 10;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM B FORCE INDEX (idx_B_c) JOIN A ON A.b = B.b WHERE A.a = 44 ORDER BY B.c LIMIT 10;
B.c
indexé?J'ai mis à jour le SQL dans le post un peu plus lisible. L'indexation devrait être clair maintenant.
OriginalL'auteur Paso | 2009-08-04
Vous devez vous connecter pour publier un commentaire.
Quand j'essaie de reproduire cette requête à l'aide de vos scripts:
il se termine dans
0.0043 seconds
(instantanément), renvoie930
les lignes et les rendements de ce plan:C'est très efficace pour une telle requête.
Pour une telle requête, vous ne pouvez pas utiliser un index unique à la fois pour le filtrage et le tri.
Voir cet article dans mon blog pour plus d'explications détaillées:
Si vous attendez de votre requête pour renvoyer quelques enregistrements, vous devez utiliser l'index sur
A
pour le filtrage et le tri à l'aide de filesort (comme la requête ci-dessus).Si vous vous attendez à ce qu'il renvoie le nombre d'enregistrements (et
LIMIT
), vous devez utiliser les index pour les trier et les filtrer:Je n'ai pas le même plan de requête que vous avez imprimé ci-dessus pour la même requête. De toute façon, la modification de la COMMANDE ne marche pas vraiment m'aider, bien sûr, il supprime la filesort mais je reçois les résultats dans le mauvais ordre! Aussi, il suffit de changer l'ORDRE dans la requête d'origine "B. b, B. c", supprime la filesort, en indiquant (pour moi ;)) qu'il pourrait être possible de le faire sans une table temporaire/filesort. (Drôle de chose, j'ai effectivement emprunté la SP pour l'insertion de votre blog)
Désolé, ne pas comprendre votre tâche. Créer un index sur
b.c
seulement et modifier leORDER BY
condition. Je vais le mettre à jour dans le post maintenant.J'ai remarqué 🙂 Content de vous entendre lire mon blog. Une petite remarque: lorsque vous remplissez un
InnoDB
table dans une procédure, toujours le faire dans une transaction, il est beaucoup plus rapide.pourriez-vous s'il vous plaît poster qui sont les plans que vous obtenez lors de l'exécution de requêtes?
OriginalL'auteur Quassnoi
select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 order by c;
Si vous alias de colonnes, est-ce que c'est? Exemple:
Les seuls changements que j'ai faits étaient les suivants:
Je sais que vos données réelles est plus complexe, mais je suppose que vous avez fourni une version simple de la requête parce que le problème est à ce niveau simple.
Êtes-vous réellement en vouloir à joindre les deux tables? Ce que je veux dire, c'est faire les deux tables lien où chaque ligne est un résultat complet basé sur la requête, ou est plus comme à chaque ligne possède les données nécessaires à partir de deux tables? Je demande parce que si les deux tables ne sont pas réellement liées ensemble de manière telle qu'une jointure est nécessaire, vous pourriez envisager une UNION à la place. Avec un SYNDICAT, les requêtes sont complètement indépendants et donc pas de sous-requêtes ou des tables temporaires ou quoi que ce soit d'autre de taxation qui doit arriver.
Je ne comprends pas vraiment. Les tables sont Jointes sur A. b = B. b et j'ai besoin de données à partir de B pour chaque assortis d'une condition, comment un SYNDICAT de l'aide ici? Pour l'exhaustivité; non je n'ai pas besoin de toutes les données, seules les données de B. Voir le tag-exemple au-dessus de la question, qu'il faut expliquer tout aussi précisément que je le peux.
OriginalL'auteur Anthony