Récemment, je suis tombé sur une application qui générait des requêtes DB. Je comprends qu'il n'y a rien de nouveau à ce sujet, mais lorsque l'application a commencé à fonctionner lentement et que j'ai dû découvrir la raison du ralentissement, j'ai été étonné de trouver ces requêtes. Voici ce à quoi SQL Server doit parfois faire face :
SELECT COUNT(DISTINCT "pr"."id") FROM ((((((((((((((((("SomeTable" "pr" LEFT OUTER JOIN "SomeTable1698" "uf_pr_id_698" ON "uf_pr_id_698"."request" = "pr"."id") LEFT OUTER JOIN "SomeTable1700" "ufref3737_i2" ON "ufref3737_i2"."request" = "pr"."id") LEFT OUTER JOIN "SomeTable1666" "x0" ON "x0"."request" = "ufref3737_i2"."f6_callerper") LEFT OUTER JOIN "SomeTable1666" "uf_ufref4646_i3_f58__666" ON "uf_ufref4646_i3_f58__666"."request" = "ufref3737_i2"."f58_") LEFT OUTER JOIN "SomeTable1694" "x1" ON "x1"."request" = "ufref3737_i2"."f38_servicep") LEFT OUTER JOIN "SomeTable3754" "ufref3754_i12" ON "pr"."id" = "ufref3754_i12"."request") LEFT OUTER JOIN "SomeTable1698" "uf_ufref3754_i12_reference_698" ON "uf_ufref3754_i12_reference_698"."request" = "ufref3754_i12"."reference") LEFT OUTER JOIN "SomeTable1698" "x2" ON "x2"."request" = "ufref3737_i2"."f34_parentse") LEFT OUTER JOIN "SomeTable4128" "ufref3779_4128_i14" ON "ufref3737_i2"."f34_parentse" = "ufref3779_4128_i14"."request") LEFT OUTER JOIN "SomeTable1859" "x3" ON "x3"."request" = "ufref3779_4128_i14"."reference") LEFT OUTER JOIN "SomeTable3758" "ufref3758_i15" ON "pr"."id" = "ufref3758_i15"."request") LEFT OUTER JOIN "SomeTable1698" "uf_ufref3758_i15_reference_698" ON "uf_ufref3758_i15_reference_698"."request" = "ufref3758_i15"."reference") LEFT OUTER JOIN "SomeTable3758" "ufref3758_i16" ON "pr"."id" = "ufref3758_i16"."request") LEFT OUTER JOIN "SomeTable4128" "ufref3758_4128_i16" ON "ufref3758_i16"."reference" = "ufref3758_4128_i16"."request") LEFT OUTER JOIN "SomeTable1859" "x4" ON "x4"."request" = "ufref3758_4128_i16"."reference") LEFT OUTER JOIN "SomeTable4128" "ufref4128_i17" ON "pr"."id" = "ufref4128_i17"."request") LEFT OUTER JOIN "SomeTable1859" "uf_ufref4128_i17_reference_859" ON "uf_ufref4128_i17_reference_859"."request" = "ufref4128_i17"."reference") LEFT OUTER JOIN "SomeTable1666" "uf_ufref4667_i25_f69__666" ON "uf_ufref4667_i25_f69__666"."request" = "uf_pr_id_698"."f69_" WHERE ("uf_pr_id_698"."f1_applicant" IN (248,169,180,201,203,205,209,215,223,357,371,379,3502,3503,3506,3514,3517,3531,3740,3741) OR "x0"."f24_useracco" IN (578872,564618,565084,566420,566422,566936,567032,567260,567689,579571,580813,594452,611522,611523,615836,621430,628371,633044,634132,634136) OR "uf_ufref4646_i3_f58__666"."f24_useracco" IN (578872,564618,565084,566420,566422,566936,567032,567260,567689,579571,580813,594452,611522,611523,615836,621430,628371,633044,634132,634136) OR "uf_ufref4667_i25_f69__666"."f24_useracco" IN (578872,564618,565084,566420,566422,566936,567032,567260,567689,579571,580813,594452,611522,611523,615836,621430,628371,633044,634132,634136) OR ("uf_pr_id_698"."f10_status" Is Null OR "uf_pr_id_698"."f10_status" <> 111) AND "ufref3737_i2"."f96_" = 0 AND (("ufref3737_i2"."f17_source" Is Null OR "ufref3737_i2"."f17_source" <> 566425) AND ("ufref3737_i2"."f17_source" Is Null OR "ufref3737_i2"."f17_source" <> 566424) OR ("uf_pr_id_698"."f10_status" Is Null OR "uf_pr_id_698"."f10_status" <> 56) ) AND ("uf_pr_id_698"."f12_responsi" IN (578872,564618,565084,566420,566422,566936,567032,567260,567689,579571,580813,594452,611522,611523,615836,621430,628371,633044,634132,634136) OR "x1"."f19_restrict" IN (578872,564618,565084,566420,566422,566936,567032,567260,567689,579571,580813,594452,611522,611523,615836,621430,628371,633044,634132,634136) OR "uf_ufref3754_i12_reference_698"."f12_responsi" IN (578872,564618,565084,566420,566422,566936,567032,567260,567689,579571,580813,594452,611522,611523,615836,621430,628371,633044,634132,634136) OR "x2"."f12_responsi" IN (578872,564618,565084,566420,566422,566936,567032,567260,567689,579571,580813,594452,611522,611523,615836,621430,628371,633044,634132,634136) OR "x3"."f5_responsib" IN (578872,564618,565084,566420,566422,566936,567032,567260,567689,579571,580813,594452,611522,611523,615836,621430,628371,633044,634132,634136) OR "uf_ufref3758_i15_reference_698"."f12_responsi" IN (578872,564618,565084,566420,566422,566936,567032,567260,567689,579571,580813,594452,611522,611523,615836,621430,628371,633044,634132,634136) OR "x4"."f5_responsib" IN (578872,564618,565084,566420,566422,566936,567032,567260,567689,579571,580813,594452,611522,611523,615836,621430,628371,633044,634132,634136) OR "uf_ufref4128_i17_reference_859"."f5_responsib" IN (578872,564618,565084,566420,566422,566936,567032,567260,567689,579571,580813,594452,611522,611523,615836,621430,628371,633044,634132,634136)) AND ("uf_pr_id_698"."f12_responsi" Is Null OR "uf_pr_id_698"."f12_responsi" <> 579420) ) AND "pr"."area" IN (700) AND "pr"."area" IN (700) AND "pr"."deleted_by_user"=0 AND "pr"."temporary" = 0
Les noms des objets ont été modifiés.
La chose la plus frappante était que le même tableau était utilisé plusieurs fois, et le nombre de parenthèses me rendait tout simplement fou. Je n'étais pas le seul à ne pas aimer ce code, SQL Server ne l'appréciait pas non plus et a dépensé beaucoup de ressources pour construire un plan pour cela. La requête peut s'exécuter pendant 50 à 150 ms et la création du plan peut prendre jusqu'à 2,5 ms. Aujourd'hui, je n'examinerai pas les moyens de résoudre le problème, mais je dirai une chose :dans mon cas, il était impossible de résoudre la génération de requêtes dans l'application.
Au lieu de cela, j'aimerais analyser les raisons pour lesquelles SQL Server construit le plan de requête pendant si longtemps. Dans n'importe quel SGBD, y compris SQL Server, le principal problème d'optimisation est la méthode de jointure des tables les unes avec les autres. Outre la méthode de jointure, la séquence des jointures de table est très importante.
Parlons de la séquence des jointures de table en détail. Il est très important de comprendre que le nombre possible de jointures de table croît de manière exponentielle et non linéaire. Par exemple Fox, il n'y a que 2 méthodes possibles pour joindre 2 tables, et le nombre peut atteindre 12 méthodes pour 3 tables. Différentes séquences de jointure peuvent avoir un coût de requête différent et l'optimiseur SQL Server doit sélectionner la méthode la plus optimale. Mais lorsque le nombre de tables est élevé, cela devient une tâche gourmande en ressources. Si SQL Server commence à passer en revue toutes les variantes possibles, une telle requête peut ne jamais être exécutée. C'est pourquoi, SQL Server ne le fait jamais et recherche toujours un assez bon plan, pas le meilleur plan. SQL Server essaie toujours de trouver un compromis entre le temps d'exécution et la qualité du plan.
Voici un exemple de la croissance exponentielle des méthodes de jointure. SQL Server peut sélectionner différentes méthodes de jointure (arborescences profondes à gauche, profondes à droite, touffues). Visuellement, il se présente de la manière suivante :
Le tableau ci-dessous présente les méthodes de jointure possibles lorsque le nombre de tables augmente :
Vous pouvez obtenir ces valeurs par vous-même :
Pour en profondeur à gauche : 5 ! =5 x 4 x 3 x 2 x 1 =120
Pour arbre touffu : (2n–2)!/(n–1)!
Conclusion :Portez une attention particulière au nombre de JOIN et ne gênez pas l'optimiseur. Si vous ne parvenez pas à obtenir le résultat souhaité dans la requête contenant plusieurs JOIN, divisez-le en plusieurs petites requêtes et vous serez surpris du résultat.
P.S. Bien sûr, nous devons comprendre qu'en plus de définir une séquence de jointures de table, l'optimiseur de requête doit également sélectionner le type de jointure, la méthode d'accès aux données (Scan, Seek) etc.
Produits utiles :
SQL Complete – écrivez, embellissez, refactorisez votre code facilement et augmentez votre productivité.