Nyligen stötte jag på en applikation som genererade DB-frågor. Jag förstår att det inte finns något nytt med det, men när programmet började köra långsamt och jag var tvungen att ta reda på orsaken till nedgången, blev jag förvånad över att hitta dessa frågor. Här är vad SQL Server ibland måste hantera:
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
Namnen på objekten har ändrats.
Det mest slående var att samma bord användes flera gånger, och antalet parenteser gjorde mig helt enkelt galen. Jag var inte den enda som inte gillade den här koden, SQL Server uppskattade den inte lika bra och spenderade mycket resurser på att bygga en plan för den. Frågan kan köras i 50-150 ms och att skapa planen kan ta upp till 2,5 ms. I dag kommer jag inte att överväga sätten att lösa problemet, men jag ska berätta en sak – i mitt fall var det omöjligt att fixa frågegenerering i applikationen.
Istället skulle jag vilja analysera anledningarna till varför SQL Server bygger frågeplanen så länge. I alla DBMS, inklusive SQL Server, är det huvudsakliga optimeringsproblemet metoden att sammanfoga tabeller med varandra. Förutom kopplingsmetoden är sekvensen av tabellkopplingar mycket viktig.
Låt oss prata om sekvensen av tabellanslutningar i detalj. Det är mycket viktigt att förstå att det möjliga antalet tabellkopplingar växer exponentiellt, inte linjärt. Fox exempel, det finns bara 2 möjliga metoder för att sammanfoga 2 tabeller, och antalet kan nå 12 metoder för 3 tabeller. Olika sammanfogningssekvenser kan ha olika frågekostnad, och SQL Server Optimizer måste välja den mest optimala metoden. Men när antalet bord är högt blir det en resurskrävande uppgift. Om SQL Server börjar gå igenom alla möjliga varianter kanske en sådan fråga aldrig körs. Det är därför, SQL Server aldrig gör det och letar alltid efter en ganska bra plan, inte den bästa planen. SQL Server försöker alltid nå en kompromiss mellan exekveringstid och plankvalitet.
Här är ett exempel på den exponentiella tillväxten av sammanfogningsmetoder. SQL Server kan välja olika sammanfogningsmetoder (vänsterdjupt, högerdjupt, buskiga träd). Visuellt ser det ut på följande sätt:
Tabellen nedan visar möjliga kopplingsmetoder när antalet tabeller ökar:
Du kan få dessa värden på egen hand:
För left-deep: 5! =5 x 4 x 3 x 2 x 1 =120
För buskigt träd: (2n–2)!/(n–1)!
Slutsats :Var särskilt uppmärksam på antalet JOINs och kom inte i vägen med optimizer. Om du misslyckas med att få önskat resultat i frågan som innehåller flera JOINs, bryt den i flera små frågor och du kommer att bli överraskad med resultatet.
P.S. Naturligtvis måste vi förstå att förutom att definiera en sekvens av tabellkopplingar, måste frågeoptimeraren också välja kopplingstyp, dataåtkomstmetoden (Scan, Seek) etc.
Användbara produkter:
SQL Complete – skriv, försköna, omstrukturera din kod enkelt och öka din produktivitet.