Kürzlich stieß ich auf eine Anwendung, die DB-Abfragen generierte. Ich verstehe, dass das nichts Neues ist, aber als die Anwendung langsam lief und ich den Grund für die Verlangsamung herausfinden musste, war ich erstaunt, diese Abfragen zu finden. Hier ist, womit SQL Server manchmal zu tun hat:
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
Namen der Objekte wurden geändert.
Das Auffälligste war, dass dieselbe Tabelle mehrfach verwendet wurde und die Anzahl der Klammern mich einfach verrückt gemacht hat. Ich war nicht der einzige, der diesen Code nicht mochte, SQL Server schätzte ihn auch nicht und verbrachte viel Ressourcen, um einen Plan dafür zu erstellen. Die Abfrage kann 50-150 ms dauern, und die Planerstellung kann bis zu 2,5 ms dauern. Heute werde ich nicht darüber nachdenken, wie das Problem behoben werden kann, aber eines sage ich – in meinem Fall war es unmöglich, die Abfragegenerierung in der Anwendung zu beheben.
Stattdessen möchte ich die Gründe analysieren, warum SQL Server den Abfrageplan so lange erstellt. In jedem DBMS, einschließlich SQL Server, ist das Hauptoptimierungsproblem die Methode, Tabellen miteinander zu verknüpfen. Neben der Join-Methode ist die Reihenfolge der Tabellen-Joins sehr wichtig.
Lassen Sie uns über die Reihenfolge der Tabellenverknüpfungen im Detail sprechen. Es ist sehr wichtig zu verstehen, dass die mögliche Anzahl von Tabellenverknüpfungen exponentiell und nicht linear wächst. Fox Beispiel, es gibt nur 2 mögliche Methoden, um 2 Tische zu verbinden, und die Anzahl kann 12 Methoden für 3 Tische erreichen. Unterschiedliche Join-Sequenzen können unterschiedliche Abfragekosten haben, und der SQL Server-Optimierer muss die optimale Methode auswählen. Aber wenn die Anzahl der Tabellen hoch ist, wird es zu einer ressourcenintensiven Aufgabe. Wenn SQL Server beginnt, alle möglichen Varianten durchzugehen, wird eine solche Abfrage möglicherweise nie ausgeführt. Aus diesem Grund tut SQL Server dies nie und sucht immer nach einem ziemlich guten Plan, nicht nach dem besten Plan. SQL Server versucht immer, einen Kompromiss zwischen Ausführungszeit und Planqualität zu finden.
Hier ist ein Beispiel für das exponentielle Wachstum von Join-Methoden. SQL Server kann verschiedene Join-Methoden auswählen (linkstief, rechtstief, buschige Bäume). Visuell sieht es folgendermaßen aus:
Die folgende Tabelle zeigt die möglichen Join-Methoden, wenn die Anzahl der Tabellen zunimmt:
Sie können diese Werte selbst abrufen:
Für linkstief: 5! =5 x 4 x 3 x 2 x 1 =120
Für buschiger Baum: (2n–2)!/(n–1)!
Fazit :Achten Sie besonders auf die Anzahl der JOINs und stören Sie sich nicht am Optimierer. Wenn Sie in der Abfrage mit mehreren JOINs nicht das gewünschte Ergebnis erhalten, teilen Sie sie in mehrere kleine Abfragen auf und Sie werden vom Ergebnis überrascht sein.
P.S. Natürlich müssen wir verstehen, dass der Abfrageoptimierer neben der Definition einer Folge von Tabellen-Joins auch den Join-Typ, die Datenzugriffsmethode (Scan, Seek) usw. auswählen muss.
Nützliche Produkte:
SQL Complete – schreiben, verschönern, refaktorisieren Sie Ihren Code ganz einfach und steigern Sie Ihre Produktivität.