sql >> Databáze >  >> RDS >> Database

Proč jsou vícenásobná připojení špatná pro dotaz nebo nebrání optimalizaci

Nedávno jsem narazil na aplikaci, která generovala DB dotazy. Chápu, že na tom není nic nového, ale když se aplikace rozběhla pomalu a já jsem musel zjišťovat příčinu zpomalení, s úžasem jsem našel tyto dotazy. Zde je to, s čím se SQL Server někdy musí vypořádat:

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

Názvy objektů byly změněny.

Nejpozoruhodnější bylo, že stejná tabulka byla použita vícekrát a počet závorek mě jednoduše přitahoval. Nebyl jsem jediný, komu se tento kód nelíbil, SQL Server to také neocenil a vynaložil mnoho prostředků na vytvoření plánu pro něj. Dotaz může běžet 50–150 ms a vytvoření plánu může trvat až 2,5 ms. Dnes nebudu zvažovat způsoby, jak problém vyřešit, ale řeknu jednu věc – v mém případě nebylo možné opravit generování dotazů v aplikaci.

Místo toho bych rád analyzoval důvody, proč SQL Server tak dlouho sestavuje plán dotazů. V každém DBMS, včetně SQL Sever, je hlavním problémem optimalizace způsob vzájemného spojování tabulek. Kromě metody spojení je velmi důležitá posloupnost spojení tabulek.

Promluvme si podrobně o posloupnosti spojení tabulek. Je velmi důležité pochopit, že možný počet spojení tabulek roste exponenciálně, nikoli lineárně. Fox příklad, existují pouze 2 možné způsoby spojení 2 tabulek a počet může dosáhnout 12 metod pro 3 tabulky. Různé sekvence spojení mohou mít různé náklady na dotaz a optimalizátor SQL Server musí vybrat nejoptimálnější metodu. Ale když je počet tabulek vysoký, stává se to úkol náročný na zdroje. Pokud SQL Server začne přecházet přes všechny možné varianty, takový dotaz nemusí být nikdy proveden. To je důvod, proč to SQL Server nikdy nedělá a vždy hledá docela dobrý plán, ne ten nejlepší. SQL Server se vždy snaží dosáhnout kompromisu mezi dobou provádění a kvalitou plánu.

Zde je příklad exponenciálního růstu metod spojení. SQL Server může vybrat různé metody spojení (doleva-hluboka, doprava-hluboka, křovinaté stromy). Vizuálně to vypadá následovně:

Níže uvedená tabulka ukazuje možné metody spojení, když se počet tabulek zvýší:

Tyto hodnoty můžete získat sami:

Pro hloubku doleva: 5! =5 x 4 x 3 x 2 x 1 =120

Pro huňatý strom: (2n–2)!/(n–1)!

Závěr :Věnujte zvláštní pozornost počtu JOINů a nepleťte si s optimalizátorem. Pokud se vám nepodaří získat požadovaný výsledek v dotazu obsahujícím více JOINů, rozdělte jej na několik malých dotazů a výsledkem budete překvapeni.

P.S. Samozřejmě musíme pochopit, že kromě definování posloupnosti spojení tabulek musí optimalizátor dotazů také vybrat typ spojení, metodu přístupu k datům (Scan, Seek) atd.

Užitečné produkty:

SQL Complete – pište, zkrášlujte, refaktorujte svůj kód snadno a zvyšte svou produktivitu.


  1. Jaký je význam otazníku v MySQL ve sloupci WHERE =??

  2. Použití Microsoft DiskSpd k testování úložného subsystému

  3. Psql se nemohl připojit k serveru:Žádný takový soubor nebo adresář, chyba 5432?

  4. Vložit výsledky uložené procedury do dočasné tabulky