2009-09-16 4 views
4

J'ai une table files avec des fichiers et une table reades avec accès en lecture à ces fichiers. Dans la table reades il y a une colonne file_id où se réfère à la colonne respective dans files.Une sous-requête qui devrait être indépendante ne l'est pas. Pourquoi?

Maintenant, je voudrais lister tous les fichiers qui ne sont pas accessibles et essayé ceci:

SELECT * FROM files WHERE file_id NOT IN (SELECT file_id FROM reades) 

Ceci est terriblement lent. La raison en est que mySQL pense que la sous-requête dépend de la requête:

+----+--------------------+--------+------+---------------+------+---------+------+------+----------+-------------+ 
| id | select_type  | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra  | 
+----+--------------------+--------+------+---------------+------+---------+------+------+----------+-------------+ 
| 1 | PRIMARY   | files | ALL | NULL   | NULL | NULL | NULL | 1053 | 100.00 | Using where | 
| 2 | DEPENDENT SUBQUERY | reades | ALL | NULL   | NULL | NULL | NULL | 3242 | 100.00 | Using where | 
+----+--------------------+--------+------+---------------+------+---------+------+------+----------+-------------+ 

Mais pourquoi? La sous-requête est complètement indépendante et plus ou moins destinée à renvoyer une liste d'identifiants.

(Pour être précis: Chaque file_id peut apparaître plusieurs fois dans reades, bien sûr, comme il peut y avoir un nombre arbitraire d'opérations de lecture pour chaque fichier.)

+0

Ceci est un [bug] (http://bugs.mysql.com/bug.php?id=63701) dans MySQL. Il est corrigé à partir de MySQL 5.6.3. – rustyx

Répondre

4

Essayez de remplacer le sous-requête avec une jointure:

SELECT * 
FROM files f 
LEFT OUTER JOIN reades r on r.file_id = f.file_id 
WHERE r.file_id IS NULL 

Voici un lien vers un article about this problem. L'auteur de cet article a écrit une procédure stockée pour forcer MySQL à évaluer les sous-requêtes comme indépendantes. Je doute que ce soit nécessaire dans ce cas cependant.

+0

Hm. EXPLAIN n'identifie aucune requête corrélée, mais elle reste très lente. – fuenfundachtzig

+0

Cela devrait être rapide sur n'importe quelle machine décente. Envisagez d'ajouter un index sur reades.file_id ou en en faisant une clé étrangère. – Andomar

+0

J'ai ajouté une clé étrangère (ALTER TABLE lit ADD FOREIGN KEY (file_id) REFERENCES fichiers (file_id);) et c'est beaucoup plus rapide maintenant. (Bien que je pensais que mySQL ne supportait pas les clés étrangères?!) – fuenfundachtzig

2

Essayez:

SELECT * FROM files WHERE file_id NOT IN (SELECT reades.file_id FROM reades) 

C'est: Si c'est à cause de l'ambiguïté de ce à quoi se réfère file_id, alors essayons de le qualifier complètement.

Si cela ne fonctionne pas, il suffit de faire:

SELECT files.* 
FROM files 
LEFT JOIN reades 
USING (file_id) 
WHERE reades.file_id IS NULL 
+0

Okay. Ouais, je ne sais pas pourquoi ça serait. – chaos

+0

La première suggestion est également reconnue en tant que sous-requête dépendante. La seconde fonctionne, mais elle est lente. – fuenfundachtzig

+0

Eh bien, il me semble que l'analyseur MySQL est volontairement mal comprendre la requête - ou la note 'SUBPERY DEPENDANT' signifie quelque chose de différent de 'sous-requête corrélée', qui est le terme normalement utilisé pour une sous-requête qui dépend d'un 'valeur de ligne actuelle' de la requête principale. –

4

J'ai déjà vu ça. c'est un bug dans mysql. essayez ceci:

SELECT * FROM files WHERE file_id NOT IN (SELECT * FROM (SELECT file_id FROM reades)) 

il rapport de bogue est ici: http://bugs.mysql.com/bug.php?id=25926

+1

a trouvé: http://bugs.mysql.com/bug.php?id=25926 – longneck

+0

+1 Lien intéressant; vous pouvez éditer votre question, au lieu d'ajouter le lien comme commentaire. – Andomar

0

Est-ce que le support de MySQL EXISTE de la même manière que MSSQL serait? Si oui, vous pouvez réécrire la requête comme

SELECT * FROM fichiers comme f OÙ file_id NOT EXISTS (SELECT 1 DE reades r OÙ r.file_id = f.file_id)

L'utilisation IN est horriblement inefficace car elle exécute cette sous-requête pour chaque ligne de la requête parent.

0

En regardant this page J'ai trouvé deux solutions possibles qui fonctionnent toutes les deux.Juste pour être complet ajouter un de ceux, similaires aux réponses ci-dessus avec SE JOINT, mais il est rapide même sans utiliser les clés étrangères:

SELECT * FROM files AS f 
    INNER JOIN (SELECT DISTINCT file_id FROM reades) AS r 
    ON f.file_id = r.file_id 

Cela résout le problème, mais cela ne répond pas à ma question :)

EDIT: Si j'interprète correctement la sortie EXPLIQUER, c'est rapide, parce que l'interprète génère un index temporaire:

+----+-------------+------------+--------+---------------+---------+---------+-----------+------+--------------------------+ 
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra     | 
+----+-------------+------------+--------+---------------+---------+---------+-----------+------+--------------------------+ 
| 1 | PRIMARY  | <derived2> | ALL | NULL   | NULL | NULL | NULL  | 843 |       | 
| 1 | PRIMARY  | f   | eq_ref | PRIMARY  | PRIMARY | 4  | r.file_id | 1 |       | 
| 2 | DERIVED  | reades  | range | NULL   | file_id | 5  | NULL  | 811 | Using index for group-by | 
+----+-------------+------------+--------+---------------+---------+---------+-----------+------+--------------------------+ 
+0

Si vous devez exécuter DISTINCT pour votre 'READES.file_id', nous ne le saurons pas. –

+0

Est-ce que "MySQL a un optimiseur de requête bâclé" répond à votre question? : P – Andomar

+0

Je n'aurais pas imaginé que l'indexation d'une colonne ait un tel impact dans ce genre de requêtes ... Je pensais que mySQL reconnaîtrait la sous-requête comme constante, mais il faut évidemment faire attention à de telles hypothèses. – fuenfundachtzig

0

IN-sous-requêtes sont en MySQL 5.5 et plus tôt converti Exister sous-requêtes. La requête donnée sera convertie à la requête suivante:

SELECT * FROM fichiers OU PAS exists (CHOISIR 1 À PARTIR reades OÙ reades.filed_id = files.file_id)

Comme vous le voyez, la sous-requête est en fait dépendante . MySQL 5.6 peut choisir de matérialiser la sous-requête. C'est, d'abord, exécuter la requête interne et stocker le résultat dans une table temporaire (suppression des doublons). Ensuite, il peut utiliser une opération de type jointure entre la table externe (c'est-à-dire les fichiers) et la table temporaire pour trouver les lignes sans correspondance. Cette façon d'exécuter la requête sera probablement plus optimale si reades.file_id n'est pas indexé. Cependant, si reades.file_id est indexé, la stratégie d'exécution traditionnelle IN-EXISTS est en fait assez efficace. Dans ce cas, je ne m'attendrais pas à une amélioration significative des performances de la conversion de la requête en une jointure comme suggéré dans d'autres réponses. L'optimiseur MySQL 5.6 fait un choix basé sur les coûts entre la matérialisation et l'exécution IN-EXISTS.

Questions connexes