2009-09-02 13 views
27

Ok Je dois créer une requête basée sur une entrée utilisateur pour filtrer les résultats.Quelle est la gravité de ma requête?

La requête va fondamentalement quelque chose comme ceci:

SELECT * FROM my_table ORDER BY ordering_fld; 

Il y a quatre zones de texte dans lequel les utilisateurs peuvent choisir de filtrer les données, ce qui signifie que je dois construire dynamiquement une clause « WHERE » dans pour le premier filtre utilisé et ensuite les clauses "ET" pour chaque filtre suivant entré. Parce que je suis trop paresseux pour faire cela, je viens de faire de chaque filtre une clause "AND" et de mettre une clause "WHERE 1" dans la requête par défaut.

Alors maintenant, j'ai:

SELECT * FROM my_table WHERE 1 {AND filters} ORDER BY ordering_fld; 

Donc ma question est, ai-je fait quelque chose qui aura une incidence défavorable sur la performance de ma requête ou quoi que ce soit d'autre enculé de quelque façon que je devrais être inquiet à distance?

+0

Vraiment question intéressante –

+10

Est-ce que ma requête semble grande dans ce domaine? –

+6

Est-ce juste moi ou Evernoob est-il extrêmement courageux? Je ne voudrais jamais demander à un site plein d'autres développeurs (qui sont notoirement connus) à quel point mon code était mauvais? –

Répondre

37

MySQL va optimiser votre 1.

J'ai juste couru cette requête sur ma base de données de test:

EXPLAIN EXTENDED 
SELECT * 
FROM t_source 
WHERE 1 AND id < 100 

et il m'a donné la description suivante:

select `test`.`t_source`.`id` AS `id`,`test`.`t_source`.`value` AS `value`,`test`.`t_source`.`val` AS `val`,`test`.`t_source`.`nid` AS `nid` from `test`.`t_source` where (`test`.`t_source`.`id` < 100) 

Comme vous pouvez le voir, pas du tout 1.

La documentation sur WHERE clause optimization en MySQL mentionne ceci:

  • pliage constante:

    (a<b AND b=c) AND a=5 
    -> b>5 AND b=c AND a=5 
    
  • Suppression des conditions constantes (nécessaire en raison des constantes):

    (B>=5 AND B=5) OR (B=6 AND 5=5) OR (B=7 AND 5=6) 
    -> B=5 OR B=6 
    

Notez les références 5 = 5 et 5 = 6 dans l'exemple ci-dessus.

+0

+ 1 pour l'essayer, en utilisant EXPLAIN et en consultant la documentation – knittl

+8

'@ knittl': Si chaque développeur utilisait' EXPLAIN' et recherchait de la documentation, le monde serait un endroit beaucoup plus agréable! – Quassnoi

+0

+1 pour essayer de rendre le monde un endroit plus agréable :) – waqasahmed

2

Pour améliorer les performances, utilisez les index de colonne sur les champs écouter « Où »

2

Avertissements standard d'injection SQL ici ...

Une chose que vous pourriez faire, pour éviter l'injection SQL puisque vous savez qu'il est seulement quatre paramètres est utiliser une procédure stockée où vous passez des valeurs pour les champs ou NULL. Je ne suis pas sûr de mySQL stockée syntaxe proc, mais la requête bouillirait jusqu'à

SELECT * 
    FROM my_table 
WHERE Field1 = ISNULL(@Field1, Field1) 
    AND Field2 = ISNULL(@Field2, Field2) 
    ... 
ORDRE BY ordering_fld 
+0

Cela tend à empêcher l'utilisation d'index sur field1, etc, devrait également utiliser COALESCE plutôt que ISNULL –

+0

Le format correct serait @Field IS NULL OU t.col = @Field. C'est une recherche d'index gaspillée ou une analyse de table autrement. –

8

Vous pouvez EXPLIQUER votre requête:
http://dev.mysql.com/doc/refman/5.0/en/explain.html

et voir si elle fait quelque chose différemment, ce dont je doute. J'utiliserais 1 = 1, juste pour que ce soit plus clair.

Vous voudrez peut-être ajouter LIMIT 1000 ou quelque chose, quand aucun paramètre n'est utilisé et que la table devient grande, voulez-vous vraiment tout retourner?

+0

'@ KM': selon les balises, cela devrait être' LIMIT 1000' :) – Quassnoi

+0

@Quassnoi, merci, je pensais mysql quand j'ai d'abord répondu, mais quand j'ai édité et ajouté la partie _TOP 1000_ je pensais que le serveur sql . –

4

S'il existe un bon moyen dans la langue de votre choix pour éviter de créer vous-même le langage SQL, utilisez-le à la place. J'aime Python et Django, et l'ORM de Django rend très facile le filtrage des résultats en fonction de l'entrée de l'utilisateur.

Si vous êtes déterminé à créer le SQL vous-même, assurez-vous de désinfecter les entrées utilisateur contre l'injection SQL et essayez d'encapsuler la construction SQL dans un module distinct de votre logique de filtre. Les performances de la requête ne devraient pas vous concerner tant que cela ne deviendra pas un problème, ce qui ne sera probablement pas le cas avant que vous n'ayez des milliers ou des millions de lignes. Et quand vient le temps d'optimiser, ajouter quelques index sur les colonnes utilisées pour WHERE et JOIN va loin.

5

WHERE 1 est une expression déterministe constante qui sera "optimisée" par tout moteur de base de données décent.

2

Nous avons fait quelque chose de similaire pas trop longtemps et il ne quelques petites choses que nous avons observées:

  • Configuration des index sur les colonnes, nous étions (peut-être) le filtrage, l'amélioration des performances
  • La partie WHERE 1 peut être complètement supprimée si les filtres ne sont pas utilisés. (Je ne sais pas si cela s'applique à votre cas) Cela ne fait pas de différence, mais «sent» bien.
  • injection SQL ne doit pas oublier

De plus, si vous ne 'avez 4 filtres, vous pouvez construire une procédure stockée et passer des valeurs nulles et de vérifier pour eux. (Comme n8wrl suggéré dans l'intervalle)

+0

Je ne suis pas trop inquiet au sujet des injections SQL, je gère cela.C'était plus, j'étais juste curieux de l'acceptabilité de ce que j'avais fait. – Evernoob

2

Cela fonctionne - quelques considérations:

SQL A propos construit dynamiquement en général, certaines bases de données (Oracle au moins) en cache des plans d'exécution des requêtes, donc si vous finissez en exécutant la même requête plusieurs fois, il n'aura pas à recommencer complètement à zéro. Si vous utilisez un SQL construit de façon dynamique, vous créez une requête différente à chaque fois, de sorte que la base de données ressemblera à 100 requêtes différentes au lieu de 100 exécutions de la même requête.

Vous auriez probablement juste besoin de mesurer la performance pour savoir si cela fonctionne assez bien pour vous.

Avez-vous besoin de toutes les colonnes? Explicitement est probablement les spécifier mieux que d'utiliser * de toute façon, parce que:

  • Vous pouvez voir visuellement les colonnes sont retournées
  • Si vous ajoutez ou supprimez des colonnes à la table plus tard, ils ne changeront pas votre interface
+0

Oracle vérifie le cache en fonction du contenu de la requête soumise, et non du fait qu'un utilisateur a exécuté une fonction ou une procédure donnée. –

2

Pas mal, je ne connaissais pas cet extrait pour se débarrasser de la question «est-ce le premier filtre 3».Alors vous devriez avoir honte de votre code (^^), cela ne fait rien à la performance car tout moteur DB l'optimisera.

+0

haha, pourquoi devrais-je avoir honte de mon code? Si le moteur DB l'optimise, il n'y a pas de mal à faire du côté DB et ma logique métier est maintenant plus claire et plus lisible pour le prochain type. Alors, où est la honte? – Evernoob

+0

Parce que vous ajoutez "1 = 1", ce qui est un code inutile, ce qui est mauvais ... Inoffensif mais mauvais. Mais tout le monde fait ça ^^ –

2

La seule raison pour laquelle j'ai utilisé WHERE 1 = 1 est pour SQL dynamique; c'est un hack pour faciliter l'ajout de clauses WHERE en utilisant AND .... Ce n'est pas quelque chose que j'inclurais dans mon SQL autrement - cela n'affecte en rien la requête parce qu'elle est toujours vraie et ne frappe pas la ou les tables impliquées, donc il n'y a pas de recherche d'index ou d'analyse de table basée sur il.

Je ne peux pas dire comment MySQL gère les critères facultatifs, mais je sais que l'utilisation de ce qui suit:

WHERE (@param IS NULL OR t.column = @param) 

... est la façon typique de la manipulation des paramètres facultatifs. COALESCE et ISNULL ne sont pas idéaux car la requête utilise toujours des index (ou pire, des analyses de table) en fonction d'une valeur sentinelle. L'exemple fourni ne touchera pas la table à moins qu'une valeur ait été fournie. Cela dit, mon expérience avec Oracle (9i, 10g) a montré qu'il ne gère pas très bien [WHERE (@param IS NULL OR t.column = @param)]. J'ai vu un énorme gain de performance en convertissant le SQL en dynamique, et j'ai utilisé des variables CONTEXT pour déterminer ce qu'il fallait ajouter. Mon impression de SQL Server 2005 est que ceux-ci sont mieux gérés.

2

J'ai souvent fait quelque chose comme ceci:

for(int i=0; i<numConditions; i++) { 
    sql += (i == 0 ? "WHERE " : "AND "); 
    sql += dbFieldNames[i] + " = " + safeVariableValues[i]; 
} 

Makes la requête généré un peu plus propre.

2

Une alternative que je l'utilise parfois est de construire la clause where d'un tableau, puis les réunir:

my @wherefields; 
foreach $c (@conditionfields) { 
    push @wherefields, "$c = ?", 
} 

my $sql = "select * from table"; 
if(@wherefields) { $sql.=" WHERE " . join (" AND ", @wherefields); } 

ci-dessus est écrit en Perl, mais la plupart des langues ont une sorte de rejoindre funciton.

Questions connexes