Bu neden daha hızlı ve kullanımı güvenli? (Alfabede ilk harf NEREDE)


10

Uzun lafın kısası, çok küçük bir tablodaki değerlere sahip küçük tabloları güncelliyoruz. Yakın zamanda yapılan bir testte, bu güncellemenin çalışması yaklaşık 5 dakika sürer.

Görünüşte mükemmel bir şekilde çalışan, mümkün olan en keskin optimizasyona benzeyen tökezledi! Aynı sorgu şimdi 2 dakikadan daha kısa sürede çalışıyor ve aynı sonuçları mükemmel bir şekilde üretiyor.

İşte sorgu. Son satır "optimizasyon" olarak eklenir. Sorgu zamanındaki yoğun düşüş neden? Bir şey mi kaçırıyoruz? Bu gelecekte sorunlara yol açabilir mi?

UPDATE smallTbl
SET smallTbl.importantValue = largeTbl.importantValue
FROM smallTableOfPeople smallTbl
JOIN largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(TRIM(smallTbl.last_name),TRIM(largeTbl.last_name)) = 4
    AND DIFFERENCE(TRIM(smallTbl.first_name),TRIM(largeTbl.first_name)) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(TRIM(largeTbl.last_name), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')

Teknik notlar: Test edilecek harf listesinin birkaç harfe daha ihtiyacı olabileceğinin farkındayız. "FARK" kullanılırken hata hatalarının da açık olduğunun farkındayız.

Sorgu planı (normal): https://www.brentozar.com/pastetheplan/?id=rypV84y7V
Sorgu planı ("optimizasyon" ile): https://www.brentozar.com/pastetheplan/?id=r1aC2my7E


4
Teknik notunuza küçük bir cevap: AND LEFT(TRIM(largeTbl.last_name), 1) BETWEEN 'a' AND 'z' COLLATE LATIN1_GENERAL_CI_AItüm karakterleri listelemenize ve okunması zor bir koda sahip olmanıza gerek kalmadan orada istediğinizi yapmalısınız
Erik A

İçindeki son durumun WHEREyanlış olduğu satırlarınız var mı? Özellikle karşılaştırmanın büyük / küçük harfe duyarlı olabileceğini unutmayın.
jpmc26

@ErikvonAsmuth mükemmel bir noktaya değiniyor. Ancak, küçük bir teknik not: SQL Server 2008 ve 2008 R2 için, "100" harmanlamalarını kullanmak en iyisidir (varsa kullanılan kültür / yerel ayar için). Öyle olur Latin1_General_100_CI_AI. Ve SQL Server 2012 ve daha yeni sürümler için (en azından SQL Server 2019 aracılığıyla), kullanılan yerel ayar için en yüksek sürümde Tamamlayıcı Karakter özellikli harmanlamaları kullanmak en iyisidir. Yani Latin1_General_100_CI_AI_SCbu durumda olur. 100'den büyük sürümler (şu ana kadar yalnızca Japonca) yoktur (veya gerekmez) _SC(ör Japanese_XJIS_140_CI_AI.
Solomon Rutzky

Yanıtlar:


9

Bu tablolar, indeksler, verilere bağlıdır .... Yürütme planları / io + zaman istatistiklerini karşılaştırmak mümkün olmadan söylemek zor.

Beklediğim fark, iki tablo arasında birleştirme önce ekstra filtreleme oluyor. Örneğimde, güncellemeleri tablolarımı yeniden kullanmak üzere seçimler olarak değiştirdim.

"Optimizasyon" ile yürütme planı resim açıklamasını buraya girin

Yürütme planı

Bir filtre işleminin gerçekleştiğini açıkça görüyorsunuz, test verilerimde filtrelendiği yerlerde kayıt yok ve sonuç olarak hiçbir iyileştirme yapılmadı.

"Optimizasyon" olmadan yürütme planı resim açıklamasını buraya girin

Yürütme planı

Filtre gitti, yani gereksiz kayıtları filtrelemek için birleşime güvenmemiz gerekecek.

Diğer nedenler Sorguyu değiştirmenin başka bir nedeni / sonucu, sorguyu değiştirirken daha hızlı olan yeni bir yürütme planının oluşturulması olabilir. Bunun bir örneği, farklı bir Join operatörü seçen motordur, ancak bu sadece bu noktada tahmin etmektedir.

DÜZENLE:

İki sorgu planını aldıktan sonra açıklama:

Sorgu büyük tablodan 550M Satırları okuyor ve filtreliyor. resim açıklamasını buraya girin

Yani yüklem, filtreleme işleminin çoğunu yapan, arama yüklemi değil. Sonuçta okunan verilerle sonuçlanır, ancak daha az geri gönderilir.

Sql sunucusunun farklı bir dizin (sorgu planı) kullanması / bir dizin eklenmesi bu sorunu çözebilir.

Peki, en iyileştirme sorgusu neden aynı soruna sahip değil?

Çünkü arama yerine tarama ile farklı bir sorgu planı kullanılır.

resim açıklamasını buraya girin resim açıklamasını buraya girin

Herhangi bir arama yapmadan, sadece çalışmak için 4M satırları döndürmek.

Sonraki fark

Güncelleme farkını göz ardı ederek (optimize edilmiş sorguda hiçbir şey güncellenmiyor) optimize edilmiş sorguda bir karma eşleme kullanılır:

resim açıklamasını buraya girin

Optimize edilmemiş bir iç içe döngü birleşimi yerine:

resim açıklamasını buraya girin

İç içe döngü, bir tablo küçük ve diğeri büyük olduğunda en iyisidir. Her ikisi de aynı boyuta yakın olduğu için, karma eşlemenin bu durumda daha iyi bir seçim olduğunu iddia ediyorum.

genel bakış

Optimize edilmiş sorgu resim açıklamasını buraya girin

Optimize edilmiş sorgunun planı paralellizme sahiptir, karma eşleşme birleşimini kullanır ve daha az kalıntı GÇ filtrelemesi yapması gerekir. Ayrıca, herhangi bir birleştirme satırı oluşturamayan anahtar değerleri ortadan kaldırmak için bir bitmap kullanır. (Ayrıca hiçbir şey güncellenmiyor)

resim açıklamasını buraya girin Optimize edilmemiş sorgu Optimize edilmemiş sorgunun planında paralellik yoktur, iç içe döngü birleşimi kullanır ve 550M kayıtlarında artık IO filtrelemesi yapılması gerekir. (Ayrıca güncelleme oluyor)

Optimize edilmemiş sorguyu geliştirmek için ne yapabilirsiniz?

  • Dizini, anahtar sütun listesinde ad ve soyadı olacak şekilde değiştirme:

    Dbo.largeTableOfPeople (doğum_tarihi, ad_adı, soyadı) üzerinde IX_largeTableOfPeople_birth_date_first_name_last_name CREATE

Ancak fonksiyonların kullanımı ve bu tablonun büyük olması nedeniyle bu en uygun çözüm olmayabilir.

  • İstatistikleri güncellemek, daha iyi bir plan elde etmek için yeniden derleme kullanarak.
  • (HASH JOIN, MERGE JOIN)Sorguya OPTION ekleme
  • ...

Test verileri + Kullanılan sorgular

CREATE TABLE #smallTableOfPeople(importantValue int, birthDate datetime2, first_name varchar(50),last_name varchar(50));
CREATE TABLE #largeTableOfPeople(importantValue int, birth_date datetime2, first_name varchar(50),last_name varchar(50));


set nocount on;
DECLARE @i int = 1
WHILE @i <= 1000
BEGIN
insert into #smallTableOfPeople (importantValue,birthDate,first_name,last_name)
VALUES(NULL, dateadd(mi,@i,'2018-01-18 11:05:29.067'),'Frodo','Baggins');

set @i += 1;
END


set nocount on;
DECLARE @j int = 1
WHILE @j <= 20000
BEGIN
insert into #largeTableOfPeople (importantValue,birth_Date,first_name,last_name)
VALUES(@j, dateadd(mi,@j,'2018-01-18 11:05:29.067'),'Frodo','Baggins');

set @j += 1;
END


SET STATISTICS IO, TIME ON;

SELECT  smallTbl.importantValue , largeTbl.importantValue
FROM #smallTableOfPeople smallTbl
JOIN #largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.last_name)),RTRIM(LTRIM(largeTbl.last_name))) = 4
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.first_name)),RTRIM(LTRIM(largeTbl.first_name))) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(RTRIM(LTRIM(largeTbl.last_name)), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å');

SELECT  smallTbl.importantValue , largeTbl.importantValue
FROM #smallTableOfPeople smallTbl
JOIN #largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.last_name)),RTRIM(LTRIM(largeTbl.last_name))) = 4
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.first_name)),RTRIM(LTRIM(largeTbl.first_name))) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
--AND LEFT(RTRIM(LTRIM(largeTbl.last_name)), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')




drop table #largeTableOfPeople;
drop table #smallTableOfPeople;

8

İkinci sorgunun aslında bir gelişme olduğu açık değildir.

Yürütme planları, soruda belirtilenden çok daha az dramatik bir fark gösteren QueryTimeStats içerir.

Yavaş planın geçen bir süresi vardı 257,556 ms(4 dakika 17 saniye). Hızlı plan, 190,992 ms3'lük bir paralellik derecesine rağmen geçen süreye (3 dakika 11 saniye) sahipti .

Üstelik ikinci plan, birleştirme işleminden sonra yapılacak işin olmadığı bir veritabanında çalışıyordu.

İlk Plan

resim açıklamasını buraya girin

İkinci Plan

resim açıklamasını buraya girin

Ekstra zaman, 3,5 milyon satırı güncellemek için gereken çalışma ile iyi açıklanabilir (bu satırı bulmak, sayfayı kilitlemek, güncellemeyi sayfaya yazmak ve işlem günlüğü ihmal edilemez)

Eğer aslında bu gibi ile karşılaştırılırken bu tekrarlanabilir ise, o zaman açıklama bu durumda sadece şanslı olmanızdır.

37 INkoşullu filtre , tablodaki 4,008,334'ten sadece 51 satırı elimine etti, ancak optimize edici bunun daha fazlasını ortadan kaldıracağını düşündü

resim açıklamasını buraya girin

   LEFT(TRIM(largeTbl.last_name), 1) IN ( 'a', 'à', 'á', 'b',
                                          'c', 'd', 'e', 'è',
                                          'é', 'f', 'g', 'h',
                                          'i', 'j', 'k', 'l',
                                          'm', 'n', 'o', 'ô',
                                          'ö', 'p', 'q', 'r',
                                          's', 't', 'u', 'ü',
                                          'v', 'w', 'x', 'y',
                                          'z', 'æ', 'ä', 'ø', 'å' ) 

Bu tür yanlış kardinalite tahminleri genellikle kötü bir şeydir. Bu durumda, muazzam küçümsemenin yol açtığı karma dökülmelere rağmen görünüşte (?) Sizin için daha iyi çalışan farklı şekilli (ve paralel) bir plan üretti.

TRIMSQL Server olmadan bunu temel sütun histogramında bir aralık aralığına dönüştürebilir ve çok daha doğru tahminler verebilir, ancak TRIMonunla sadece tahminlere başvurur.

Tahminin doğası değişebilir, ancak tek bir tahmine ilişkin tahmin LEFT(TRIM(largeTbl.last_name), 1)bazı durumlarda * sadece tahmin edilmektedir table_cardinality/estimated_number_of_distinct_column_values.

Tam olarak hangi koşullardan emin değilim - veri boyutu bir rol oynuyor gibi görünüyor. Bunu burada olduğu gibi sabit uzunluklu veri türleriyle çoğaltmayı başardım, ancak farklı, daha yüksek bir tahmin aldım varchar(sadece% 10'luk bir tahmin tahmini ve 100.000 satır tahmin etti). @Solomon Rutzky eğer işaret varchar(100)sonunda boşluk doldurulur için olduğu gibi chardaha düşük bir tahmin kullanılmaktadır

INListe için dışarı genişletilir ORve SQL Server kullanan üstel geri çekilme kabul 4 yüklemler maksimum. Dolayısıyla, 219.707tahmin şu şekildedir.

DECLARE @TableCardinality FLOAT = 4008334, 
        @DistinctColumnValueEstimate FLOAT = 34207

DECLARE @NotSelectivity float = 1 - (1/@DistinctColumnValueEstimate)

SELECT @TableCardinality * ( 1 - (
@NotSelectivity * 
SQRT(@NotSelectivity) * 
SQRT(SQRT(@NotSelectivity)) * 
SQRT(SQRT(SQRT(@NotSelectivity)))
))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.