DISTINCT
Yukarıdaki sorguda işlemi gerçekleştirebilecek üç farklı iyileştirici kural var gibi görünüyor . Aşağıdaki sorgu, listenin ayrıntılı olduğunu gösteren bir hata atar:
SELECT DISTINCT TOP 10 ID
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, QUERYRULEOFF GbAggToSort, QUERYRULEOFF GbAggToHS, QUERYRULEOFF GbAggToStrm);
Mesaj 8622, Seviye 16, Durum 1, Sıra 1
Sorgu işlemcisi, bu sorguda tanımlanan ipuçları nedeniyle bir sorgu planı oluşturamadı. Herhangi bir ipucu belirtmeden ve SET FORCEPLAN kullanmadan sorguyu tekrar gönderin.
GbAggToSort
Grup bazında kümeyi (farklı) farklı bir sıralama olarak uygular. Bu, herhangi bir satır üretmeden önce girdideki tüm verileri okuyan bir engelleme operatörüdür. GbAggToStrm
Grup-by toplamasını bir akış toplayıcısı olarak uygular (bu, aynı zamanda bir giriş sıralaması gerektirir). Bu aynı zamanda bir engelleme operatörüdür. GbAggToHS
Sorundan kötü planda gördüğümüz şey olan karma eşleşmeyi uygular, ancak karma eşleşmesi (toplam) veya karma eşleşmesi (farklı akış) olarak uygulanabilir.
Karma eşleşmesi ( farklı akış ) operatörü bu sorunu çözmenin bir yoludur, çünkü engellemediğinden. SQL Server, yeterli farklı değerler bulduğunda taramayı durdurabilmelidir.
Flow Distinct mantıksal operatörü girişi tarar, çiftleri siler. Distinct operatörü herhangi bir çıktı üretmeden önce tüm girdiyi tüketirken, Flow Distinct operatörü girdiden elde edilen her satırı geri döndürür (bu satır bir kopya olmadığı sürece atılır).
Neden sorudaki sorgu, karma eşleşmesi (akış farklı) yerine karma eşleştirmesini (toplu) kullanıyor? Tablodaki farklı değerlerin sayısı değiştikçe, karma eşleşme (akış farklı) sorgusunun maliyetinin düşmesini beklerim, çünkü tabloya taraması gereken satır sayısının tahmini düşmelidir. Karma eşleşmenin (toplamın) maliyetinin artmasını beklerim çünkü inşa etmesi gereken karma tablo büyür. Bunu araştırmanın bir yolu, bir plan rehberi oluşturmaktır . Verilerin iki kopyasını oluşturursam ancak bunlardan birine bir plan kılavuzu uygularsam, eşleşme eşlemesini (toplam), eşleşme eşlemesine (farklı) aynı verilere karşı yan yana karşılaştırabilirim. Aynı kural her iki plana da uygulandığından, sorgu iyileştirici kurallarını devre dışı bırakarak bunu yapamayacağımı unutmayın GbAggToHS
.
Peşinde olduğum plan rehberini almanın bir yolu:
DROP TABLE IF EXISTS X_PLAN_GUIDE_TARGET;
CREATE TABLE X_PLAN_GUIDE_TARGET (VAL VARCHAR(10) NOT NULL);
INSERT INTO X_PLAN_GUIDE_TARGET WITH (TABLOCK)
SELECT CAST(N % 10000 AS VARCHAR(10))
FROM dbo.GetNums(10000000);
UPDATE STATISTICS X_PLAN_GUIDE_TARGET WITH FULLSCAN;
-- run this query
SELECT DISTINCT TOP 10 VAL FROM X_PLAN_GUIDE_TARGET OPTION (MAXDOP 1)
Plan tanıtıcısını alın ve bir plan kılavuzu oluşturmak için kullanın:
-- plan handle is 0x060007009014BC025097E88F6C01000001000000000000000000000000000000000000000000000000000000
SELECT qs.plan_handle, st.text FROM
sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
WHERE st.text LIKE '%X[_]PLAN[_]GUIDE[_]TARGET%'
ORDER BY last_execution_time DESC;
EXEC sp_create_plan_guide_from_handle
'EVIL_PLAN_GUIDE',
0x060007009014BC025097E88F6C01000001000000000000000000000000000000000000000000000000000000;
Plan rehberleri yalnızca tam olarak sorgu metni üzerinde çalışır, bu yüzden tekrar plan kılavuzundan kopyalayalım:
SELECT query_text
FROM sys.plan_guides
WHERE name = 'EVIL_PLAN_GUIDE';
Verileri sıfırla:
TRUNCATE TABLE X_PLAN_GUIDE_TARGET;
INSERT INTO X_PLAN_GUIDE_TARGET WITH (TABLOCK)
SELECT REPLICATE(CHAR(65 + (N / 100000 ) % 10 ), 10)
FROM dbo.GetNums(10000000);
Plan rehberi uygulanmış olarak, sorgu için bir sorgu planı edinin :
SELECT DISTINCT TOP 10 VAL FROM X_PLAN_GUIDE_TARGET OPTION (MAXDOP 1)
Bu, test verilerimizle istediğimiz karma eşleme (farklı akış) operatörüne sahiptir. SQL Server'ın tablodaki tüm satırları okumayı beklediğini ve tahmini maliyetin karma eşlemeli (toplam) planla aynı olduğunu unutmayın. Yaptığım test, iki planın maliyetinin, planın satır hedefi, SQL Server'ın tablodan beklediği farklı değerlerin sayısından büyük veya ona eşit olması durumunda aynı olduğunu gösterdi. istatistikleri. Maalesef (sorgumuz için), optimizer maliyetler aynı olduğunda karma eşleşmesini (toplam) karma eşleşmesi (farklı akış) üzerinden alır. Yani 0,0000001 sihirli optimizer birimi istediğimiz plandan uzaktayız.
Bu soruna saldırmanın bir yolu kürek hedefini azaltmaktır. Görünüm noktasındaki satır hedefi, optimize edici ise, satır sıralarının sayısından daha az ise, büyük olasılıkla karma eşleşmesi elde ederiz (farklı akış). Bu OPTIMIZE FOR
sorgu ipucu ile gerçekleştirilebilir :
DECLARE @j INT = 10;
SELECT DISTINCT TOP (@j) VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, OPTIMIZE FOR (@j = 1));
Bu sorgu için optimizer, sorgu yalnızca ilk satıra ihtiyaç duyuyormuş gibi bir plan oluşturur, ancak sorgu çalıştırıldığında ilk 10 satırı geri alır. Makinemde bu sorgu 892800 satırdan tarar X_10_DISTINCT_HEAP
ve 299 msn'de 250 ms CPU zamanı ve 2537 mantıksal okuma ile tamamlar.
İstatistikler, çarpık verilere karşı örneklenmiş istatistikler için olabilecek yalnızca tek bir değeri rapor ederse bu tekniğin işe yaramayacağını unutmayın. Bununla birlikte, bu durumda, verilerinizin böyle teknikleri kullanarak haklı kılmak için yeterince yoğun bir şekilde doldurulması pek olası değildir. Tablodaki tüm verileri tarayarak, özellikle de paralel olarak yapılabiliyorsa, çok fazla kaybedemezsiniz.
Bu soruna saldırmanın bir başka yolu, SQL Server'ın temel tablodan almasını beklediği tahmin edilen farklı değerlerin sayısını şişirmektir. Bu beklenenden daha zordu. Deterministik bir işlev uygulamak, muhtemelen belirgin sonuç sayısını artıramaz. Sorgu en iyi duruma getiricisi bu matematiksel gerçeğin farkındaysa (bazı testler en azından bizim amaçlarımız için olduğunu düşünür) o zaman deterministik işlevler uygulamak ( tüm dize işlevlerini içeren ), tahmini farklı satır sayısını artırmaz.
Nondeterministic işlevlerinin çoğu bariz seçimler dahil da işe yaramadı NEWID()
ve RAND()
. Ancak, LAG()
bu sorgu için hile yapar. Sorgu en iyi duruma getiricisi LAG
, karma eşleşmesi (farklı akış) planını teşvik edecek ifadeye karşı 10 milyon farklı değer bekler :
SELECT DISTINCT TOP 10 LAG(VAL, 0) OVER (ORDER BY (SELECT NULL)) AS ID
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1);
Makinemde bu sorgu 892800 satırdan tarar X_10_DISTINCT_HEAP
ve 1165 ms'de 1109 ms CPU zamanı ve 2537 mantıksal okuma ile tamamlanır, bu yüzden LAG()
oldukça fazla bağıl ek yükü ekler. @ Paul White bu sorgu için toplu işlem modunu denemeyi önerdi. SQL Server 2016'da, toplu mod işleme ile bile çalışabiliriz MAXDOP 1
. Bir satır deposu için toplu işlem kipini almanın bir yolu boş bir CCI'ye aşağıdaki gibi katılmaktır:
CREATE TABLE #X_DUMMY_CCI (ID INT NOT NULL);
CREATE CLUSTERED COLUMNSTORE INDEX X_DUMMY_CCI ON #X_DUMMY_CCI;
SELECT DISTINCT TOP 10 VAL
FROM
(
SELECT LAG(VAL, 1) OVER (ORDER BY (SELECT NULL)) AS VAL
FROM X_10_DISTINCT_HEAP
LEFT OUTER JOIN #X_DUMMY_CCI ON 1 = 0
) t
WHERE t.VAL IS NOT NULL
OPTION (MAXDOP 1);
Bu kod, bu sorgu planında sonuçlanır .
Paul kullanmak için sorguyu değiştirmem gerektiğine işaret etti LAG(..., 1)
çünkü LAG(..., 0)
Pencere Toplamı optimizasyonu için uygun görünmüyordu. Bu değişiklik geçen süreyi 520 ms'ye, CPU süresini 454 ms'ye düşürdü.
LAG()
Yaklaşımın en istikrarlı olmadığını unutmayın . Microsoft, işleve karşı benzersizlik varsayımını değiştirirse, artık çalışmayabilir. Eski CE ile farklı bir tahmine sahiptir. Ayrıca bir yığına karşı bu tür bir optimizasyon yapılması iyi bir fikir değildir. Eğer masa yeniden kurulursa, neredeyse tüm satırların tablodan okunması gereken en kötü durum senaryosunda sonuçlanabilir.
Benzersiz bir sütunu olan bir masaya (örneğin, sorudaki kümelenmiş indeks örneği gibi) daha iyi seçeneklere sahibiz. Örneğin SUBSTRING
, her zaman boş bir dize döndüren bir ifade kullanarak en iyileştiriciyi kandırabiliriz . SQL Server, SUBSTRING
farklı değerlerin sayısını değiştireceğini düşünmüyor, bu yüzden eğer PK gibi benzersiz bir sütuna uygularsak, o zaman tahmin edilen farklı satır sayısı 10 milyondur. Bu aşağıdaki sorgu , karma eşleşmesi (farklı akış) işlecini alır:
SELECT DISTINCT TOP 10 VAL + SUBSTRING(CAST(PK AS VARCHAR(10)), 11, 1)
FROM X_10_DISTINCT_CI
OPTION (MAXDOP 1);
Makinemde bu sorgu 90000 satır tarar X_10_DISTINCT_CI
ve 293 ms CPU zamanı ve 3011 mantıksal okuma ile 333 msn'de tamamlar.
Özetle, sorgu iyileştirici SELECT DISTINCT TOP N
, N
> = tablodan tahmini farklı satır sayısı olduğunda , tüm satırların sorgular için tablodan okunacağını varsayıyor gibi görünmektedir . Karma eşleştirme (toplama) işleci karma eşleştirme (akış farklı) işleci ile aynı maliyete sahip olabilir, ancak en iyileştirici her zaman toplama işlecini seçer. Bu, tablo taramasının başlangıcına yakın yeterli miktarda değer bulunduğunda gereksiz mantıksal okumalara neden olabilir. Optimize ediciyi karma eşleşme (farklı akış) operatörünü kullanmaya aldatmanın iki yolu OPTIMIZE FOR
ipucunu kullanarak satır hedefini düşürmek LAG()
veya SUBSTRING
benzersiz bir sütunu kullanarak veya bu satırdaki tahmini farklı satır sayısını artırmaktır .