Question

Nous avons deux tableaux ressemblant à une structure simple d’enregistrement de balises (en réalité, ils sont beaucoup plus complexes, 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 consiste à récupérer les enregistrements commandés avec une balise spécifique. La façon évidente de le faire est de faire une simple jointure et d’indexer sur (PK) (A.a, A.b), (A.b), (PK) (B.b), (B.b, B.c) en tant que telles:

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 le résultat désagréable d'un port de fichiers:

+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+
| 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                                  | 
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+

Utilisation d'une "vue matérialisée" énorme et extrêmement redondante nous pouvons obtenir des performances assez décentes, mais ceci au détriment de la complication de la logique métier, ce que nous aimerions éviter, en particulier parce que les tables A et B sont déjà des tables MV: s (et sont nécessaires pour d'autres requêtes, et enfuissent les mêmes requêtes en utilisant UNION).

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;

Le fait que nous ayons des conditions sur la table B, telles que les filtres de plage, est un autre facteur de complication, ce qui complique encore la situation.

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 de pouvoir gérer cela si le problème du dossier de fichiers disparaît.

Quelqu'un sait-il pourquoi la simple jointure dans le bloc de code 3 ci-dessus n'utilise pas l'index pour le tri et si nous pouvons contourner le problème d'une manière ou d'une autre sans créer un nouveau MV?

Vous trouverez ci-dessous la liste complète des instructions SQL que nous utilisons 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;
Était-ce utile?

La solution

Lorsque j'essaie de reproduire cette requête à l'aide de vos scripts:

SELECT  A.a, A.b, B.c
FROM    A
JOIN    B
ON      A.b = B.b
WHERE   a = 44
ORDER BY
        c

, il se termine en 0,0043 seconde (instantanément), retourne des 930 lignes et génère ce plan:

1, 'SIMPLE', 'A', 'ref', 'PRIMARY', 'PRIMARY', '4', 'const', 1610, 'Using index; Using temporary; Using filesort'
1, 'SIMPLE', 'B', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.A.b', 1, ''

C'est assez efficace pour une telle requête.

Pour une telle requête, vous ne pouvez pas utiliser un seul index à la fois pour le filtrage et le tri.

Voir cet article sur mon blog pour des explications plus détaillées:

Si vous prévoyez que votre requête retourne quelques enregistrements, vous devez utiliser l'index sur A pour le filtrage, puis trier à l'aide du serveur de fichiers (comme le fait la requête ci-dessus).

Si vous attendez qu'il renvoie de nombreux enregistrements (et les LIMIT ), vous devez utiliser l'index pour le tri, puis filtrer:

CREATE INDEX ix_a_b ON a (b);
CREATE INDEX ix_b_c ON b (c)

SELECT  *
FROM    B FORCE INDEX (ix_b_c)
JOIN    A
ON      A.b = B.b
ORDER BY
        b.c
LIMIT 10;

1, 'SIMPLE', 'B', 'index', '', 'ix_b_c', '4', '', 2, 'Using index'
1, 'SIMPLE', 'A', 'ref', 'ix_a_b', 'ix_a_b', '4', 'test.B.b', 4, 'Using index'

Autres conseils

sélectionnez A.a, A.b, B.c dans A joindre B sur A.b = B.b où a = 44 ordre par c;

Si vous aliasez les colonnes, est-ce que cela vous aide? Exemple:

 SELECT 
 T1.a AS colA, 
 T2.b AS colB, 
 T2.c AS colC 
 FROM A AS T1 
 JOIN B AS T2 
 ON (T1.b = T2.b) 
 WHERE 
 T1.a = 44 
 ORDER BY colC;

Les seules modifications que j'ai apportées sont les suivantes:

  • Je mets les conditions de jointure entre parenthèses
  • Les conditions de jointure et où les conditions sont basées sur des colonnes de table
  • La condition ORDER BY est basée sur la colonne de table résultante
  • J'ai aliasé les colonnes de la table de résultat et les tables interrogées afin de (plus à espérer) mieux préciser quand j'utilisais l'une ou l'autre (et plus clairement pour le serveur. Vous omettez de faire référence à vos colonnes à deux endroits de votre requête).

Je sais que vos données réelles sont plus complexes, mais je suppose que vous avez fourni une version simple de la requête car le problème se situe à ce niveau simple.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top