Database
 sql >> Baza danych >  >> RDS >> Database

Dlaczego wiele sprzężeń JOIN jest szkodliwych dla zapytania lub nie przeszkadza w optymalizacji?

Niedawno natknąłem się na aplikację, która generowała zapytania do bazy danych. Rozumiem, że nie ma w tym nic nowego, ale kiedy aplikacja zaczęła działać wolno i musiałem znaleźć przyczynę spowolnienia, byłem zdumiony, gdy znalazłem te zapytania. Oto, z czym czasami ma do czynienia SQL Server:

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

Nazwy obiektów zostały zmienione.

Najbardziej uderzające było to, że ten sam stół był używany wielokrotnie, a liczba nawiasów po prostu doprowadzała mnie do szału. Nie byłem jedynym, któremu nie spodobał się ten kod, SQL Server też go nie docenił i wydał dużo zasobów na zbudowanie jego planu. Zapytanie może trwać 50-150 ms, a tworzenie planu może potrwać do 2,5 ms. Dzisiaj nie będę się zastanawiał nad sposobami rozwiązania problemu, ale powiem jedno – w moim przypadku nie udało się naprawić generowania zapytań w aplikacji.

Zamiast tego chciałbym przeanalizować powody, dla których SQL Server tak długo buduje plan zapytań. W każdym DBMS, w tym SQL Sever, głównym problemem optymalizacyjnym jest sposób łączenia ze sobą tabel. Oprócz metody łączenia bardzo ważna jest kolejność łączenia tabel.

Porozmawiajmy szczegółowo o sekwencji złączeń tabel. Bardzo ważne jest, aby zrozumieć, że możliwa liczba sprzężeń tabel rośnie wykładniczo, a nie liniowo. Przykład Foxa, są tylko 2 możliwe sposoby dołączenia do 2 stołów, a liczba może osiągnąć 12 metod dla 3 stołów. Różne sekwencje sprzężenia mogą mieć różne koszty zapytań, a optymalizator programu SQL Server musi wybrać najbardziej optymalną metodę. Ale gdy liczba tabel jest duża, staje się to zadaniem wymagającym dużej ilości zasobów. Jeśli SQL Server zacznie przeglądać wszystkie możliwe warianty, takie zapytanie może nigdy nie zostać wykonane. Dlatego SQL Server nigdy tego nie robi i zawsze szuka całkiem dobrego planu, a nie najlepszego planu. SQL Server zawsze stara się znaleźć kompromis między czasem wykonania a jakością planu.

Oto przykład wykładniczego wzrostu metod łączenia. SQL Server może wybrać różne metody łączenia (z lewej, z prawej, krzaczaste). Wizualnie wygląda to następująco:

Poniższa tabela pokazuje możliwe metody łączenia, gdy liczba stołów wzrasta:

Możesz samodzielnie uzyskać te wartości:

Dla od lewej: 5! =5 x 4 x 3 x 2 x 1 =120

Dla krzaczastych drzew: (2n–2)!/(n–1)!

Wniosek :Zwróć szczególną uwagę na liczbę JOIN i nie wchodź w drogę z optymalizatorem. Jeśli nie uzyskasz pożądanego wyniku w zapytaniu zawierającym wiele JOIN, podziel go na kilka małych zapytań, a będziesz zaskoczony wynikiem.

PS Oczywiście musimy zrozumieć, że oprócz zdefiniowania sekwencji złączeń tabel, optymalizator zapytań musi również wybrać typ złączenia, metodę dostępu do danych (skanowanie, wyszukiwanie) itp.

Przydatne produkty:

SQL Complete – napisz, upiększ, zrefaktoruj swój kod łatwo i zwiększ produktywność.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Naprawianie utraty danych za pomocą przesyłania dzienników z opóźnionym odzyskiwaniem

  2. Przestarzałe funkcje do wyjęcia z przybornika – część 2

  3. Minimalne logowanie za pomocą INSERT…SELECT do pustych tabel klastrowych

  4. Statyczne i dynamiczne maskowanie danych w FieldShield

  5. SQL, jak zaktualizować dane