WHERE IN kullanarak silme işlemi sırasında beklenmeyen taramalar


40

Aşağıdaki gibi bir sorgu var:

DELETE FROM tblFEStatsBrowsers WHERE BrowserID NOT IN (
    SELECT DISTINCT BrowserID FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID IS NOT NULL
)

tblFEStatsBrowsers'in 553 satırı var.
tblFEStatsPaperHits 47.974.301 satıra sahiptir.

tblFEStatsBrowsers:

CREATE TABLE [dbo].[tblFEStatsBrowsers](
    [BrowserID] [smallint] IDENTITY(1,1) NOT NULL,
    [Browser] [varchar](50) NOT NULL,
    [Name] [varchar](40) NOT NULL,
    [Version] [varchar](10) NOT NULL,
    CONSTRAINT [PK_tblFEStatsBrowsers] PRIMARY KEY CLUSTERED ([BrowserID] ASC)
)

tblFEStatsPaperHits:

CREATE TABLE [dbo].[tblFEStatsPaperHits](
    [PaperID] [int] NOT NULL,
    [Created] [smalldatetime] NOT NULL,
    [IP] [binary](4) NULL,
    [PlatformID] [tinyint] NULL,
    [BrowserID] [smallint] NULL,
    [ReferrerID] [int] NULL,
    [UserLanguage] [char](2) NULL
)

Tarayıcı Kimliği içermeyen tblFEStatsPaperHits üzerinde kümelenmiş bir dizin var. İç sorgulama yapmak, bu nedenle tamamen tamam olan bir tblFEStatsPaperHits tablo taraması gerektirir.

Şu anda, tblFEStatsBrowsers'daki her bir satır için tam bir tarama gerçekleştiriliyor, yani 553 tblFEStatsPaperHits tam tablo taraması var.

NEREDE BİR VARLIKLARA yeniden yazmak, planı değiştirmiyor:

DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
    SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
)

Ancak, Adam Machanic tarafından önerildiği gibi, bir HASH JOIN seçeneği eklemek, optimum uygulama planına neden olur (sadece bir tblFEStatsPaperHits taraması):

DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
    SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
) OPTION (HASH JOIN)

Şimdi bu, bunun nasıl düzeltileceği ile ilgili bir soru değil - OPTION (HASH JOIN) öğesini kullanabilir veya manuel olarak geçici bir tablo oluşturabilirim. Sorgu en iyi duruma getiricinin neden şu anda yaptığı planı kullandığını merak ediyorum.

QO Tarayıcı Kimliği sütununda herhangi bir istatistik bulunmadığından, sanırım en kötü - 50 milyon farklı değer olduğunu varsayıyor, bu yüzden oldukça büyük bir bellek / tempdb çalışma masası gerektiriyor. Bu nedenle, en güvenli yol her satır için tblFEStatsBrowsers'da tarama yapmaktır. İki tablodaki BrowserID sütunları arasında yabancı anahtar ilişkisi yoktur, bu nedenle QO herhangi bir bilgiyi tblFEStatsBrowsers'dan düşemez.

Bu, sesler kadar basit mi, sebep mi?

Güncelleme 1
Birkaç istatistik vermek için: OPTION (HASH JOIN):
208.711 mantıksal okuma (12 tarama)

SEÇENEK (LOOP JOIN, HASH GROUP):
11.008.698 mantıksal okumalar (~ Tarayıcı Kimliği başına tarama (339))


Seçenek yok: 11.008.775 mantıksal okumalar (~ Tarayıcı Kimliği başına tarama (339))

Güncelleme 2
Mükemmel cevaplar, hepiniz - teşekkürler! Sadece birini seçmek zor. Martin ilk olmasına rağmen ve Remus mükemmel bir çözüm sunsa da, detaylarda zihinselleşmesi için Kivi'ye vermeliyim :)



2
@ MarkStorey-Smith Sure - pastebin.com/9HHRPFgK Komut dosyasını boş bir veritabanında çalıştırdığınızı varsayarsak, bu, yürütme planını gösterirken sorunlu sorguları yeniden oluşturmamı sağlar. Her iki sorgu da betiğin sonuna dahil edilmiştir.
Mark S. Rasmussen

Yanıtlar:


61

"Sorgu en iyi duruma getiricisinin neden şu anda yaptığı planı kullandığını merak ediyorum."

Başka bir ifadeyle, aşağıdaki planın alternatiflerin (bunlardan pek çoğunun var olduğu ) ile karşılaştırıldığında, optimize ediciye en ucuz görünmesinin nedenidir .

Asıl plan

Birleşmenin iç tarafı, esasen her bir ilişkili değer için aşağıdaki formun bir sorgusunu çalıştırmaktadır BrowserID:

DECLARE @BrowserID smallint;

SELECT 
    tfsph.BrowserID 
FROM dbo.tblFEStatsPaperHits AS tfsph 
WHERE 
    tfsph.BrowserID = @BrowserID 
OPTION (MAXDOP 1);

Kağıt tarama vurur

Satırların tahmini sayısı olduğunu unutmayın 185.220 (değil 289.013 eşitlik karşılaştırması örtük dışlamalarınızın beri) NULL(sürece ANSI_NULLSolan OFF). Yukarıdaki planın tahmini maliyeti 206.8 birimdir.

Şimdi bir TOP (1)madde ekleyelim :

DECLARE @BrowserID smallint;

SELECT TOP (1)
    tfsph.BrowserID 
FROM dbo.tblFEStatsPaperHits AS tfsph 
WHERE 
    tfsph.BrowserID = @BrowserID 
OPTION (MAXDOP 1);

TOP ile (1)

Tahmini maliyet şimdi 0.00452 adettir. Top fiziksel operatörünün eklenmesi, Üst operatörde 1 satırlık bir satır hedefi belirler . Ardından soru, Kümelenmiş Dizin Taraması için bir 'satır hedefinin nasıl elde edileceği' haline gelir; yani, bir satır BrowserIDyüklemiyle eşleşmeden önce tarama işleminin kaç satır beklemesi gerekir ?

Mevcut istatistiksel bilgiler 166 farklı BrowserIDdeğer göstermektedir (1 / [Tüm Yoğunluk] = 1 / 0.006024096 = 166). Maliyetlendirme, farklı değerlerin fiziksel satırlar üzerinde eşit bir şekilde dağıldığını varsayar, bu nedenle Kümelenmiş Dizin Taramasındaki satır hedefi 166.302 olarak ayarlanır (örneklenen istatistiklerin toplanmasından bu yana tablo kardinalitesindeki değişikliği hesaba katar ).

Tahmini 166 satırı taramanın tahmini maliyeti çok büyük değil (hatta her değişiklik için bir kez 339 kez uygulandı BrowserID) - Kümelenmiş Dizin Taraması satır hedefinin ölçeklendirme etkisini gösteren tahmini bir 1.3219 birim maliyeti gösterir . G / Ç ve CPU için ölçeklendirilmemiş operatör maliyetleri sırasıyla 153.931 ve 52.8698 olarak gösterilmiştir:

Satır Hedefi Ölçekli Tahmini Maliyetler

Uygulamada, öyle çok birini mümkün her içerecektir (her ne onlar iade edilecek gerçekleşmesi sipariş) ilk 166 satır dizinden taranan olası BrowserIDdeğerler. Bununla birlikte, DELETEplan toplamda 1.40921 birim olarak fiyatlandırılmıştır ve bu nedenle optimizer tarafından seçilir. Bart Duncan, bu türün başka bir örneğini Row Goals Gone Rogue adlı yeni bir yayında gösteriyor .

Uygulama planındaki Top operatörünün , Anti Semi Join ile (özellikle 'kısa devre yapan' Martin'nin bahsettiği) ilişkili olmadığını not etmek de ilginçtir . İlk önce GbAggToConstScanOrTop adlı bir arama kuralını devre dışı bırakarak Top’un nereden geldiğini görmeye başlayabiliriz :

DBCC RULEOFF ('GbAggToConstScanOrTop');
GO
DELETE FROM tblFEStatsBrowsers 
WHERE BrowserID NOT IN 
(
    SELECT DISTINCT BrowserID 
    FROM tblFEStatsPaperHits WITH (NOLOCK) 
    WHERE BrowserID IS NOT NULL
) OPTION (MAXDOP 1, LOOP JOIN, RECOMPILE);
GO
DBCC RULEON ('GbAggToConstScanOrTop');

GbAggToConstScanOrTop Disabled

Bu planın tahmini maliyeti 364.912'dir ve Topluluğun Topluluğa Göre Grupla değiştirildiğini (ilişkili sütuna göre gruplayarak BrowserID) gösterir. Toplam, sorgu metnindeki fazlalık nedeniyle değilDISTINCT : LASJNtoLASJNonDist ve LASJOnLclDist adlı iki keşif kuralıyla getirilebilecek bir optimizasyondur . Bu ikisini de devre dışı bırakmak bu planı oluşturur:

DBCC RULEOFF ('LASJNtoLASJNonDist');
DBCC RULEOFF ('LASJOnLclDist');
DBCC RULEOFF ('GbAggToConstScanOrTop');
GO
DELETE FROM tblFEStatsBrowsers 
WHERE BrowserID NOT IN 
(
    SELECT DISTINCT BrowserID 
    FROM tblFEStatsPaperHits WITH (NOLOCK) 
    WHERE BrowserID IS NOT NULL
) OPTION (MAXDOP 1, LOOP JOIN, RECOMPILE);
GO
DBCC RULEON ('LASJNtoLASJNonDist');
DBCC RULEON ('LASJOnLclDist');
DBCC RULEON ('GbAggToConstScanOrTop');

Biriktirme Planı

Bu planın tahmini maliyeti 40729.3 birimdir.

Group By-Top'dan dönüşüm olmadan, optimizer 'doğal olarak' BrowserIDanti yarı birleşmeden önce birleştirilmiş bir karma birleştirme planı seçer :

DBCC RULEOFF ('GbAggToConstScanOrTop');
GO
DELETE FROM tblFEStatsBrowsers 
WHERE BrowserID NOT IN 
(
    SELECT DISTINCT BrowserID 
    FROM tblFEStatsPaperHits WITH (NOLOCK) 
    WHERE BrowserID IS NOT NULL
) OPTION (MAXDOP 1, RECOMPILE);
GO
DBCC RULEON ('GbAggToConstScanOrTop');

Top DOP 1 Planı Yok

MAXDOP 1 kısıtlaması olmadan, paralel bir plan:

Paralel Plan Yok

Orijinal sorguyu 'düzeltmenin' başka bir yolu BrowserID, yürütme planının rapor ettiği eksik dizini oluşturmaktır . İç içe döngüler, iç taraf endekslendiğinde en iyi şekilde çalışır. Yarı birleşme için kardinalite tahmini, en iyi ihtimalle zordur. Uygun indekslemenin olmaması (büyük tablonun benzersiz bir anahtarı bile yoktur!) Hiç yardımcı olmaz.

Paul


3
Sana boyun eğiyorum, az önce hiç karşılaşmadığım birkaç yeni konseptle tanıştırdın. Bir şeyi bildiğinizi hissettiğinizde, dışarıdaki birileri sizi yere indirecek - iyi bir şekilde :) Dizine eklemek kesinlikle yardımcı olacaktır. Bununla birlikte, bu tek seferlik işlemin yanı sıra, alana Tarayıcı Tarayıcı sütunu tarafından hiçbir zaman erişilmez / toplanmaz ve bu yüzden bu baytları tablo oldukça büyük olduğu için kaydetmeyi tercih ederim (bu, aynı veritabanlarından yalnızca biridir). Masanın üzerinde benzersiz bir anahtar yoktur, çünkü doğal bir özgünlük yoktur. Tüm seçimler PaperID ve isteğe bağlı olarak bir süredir.
Mark S. Rasmussen

22

Senaryoyu çalıştırdığımda sadece istatistik bir veritabanı oluşturmak ve soruyu sorgulamak için aşağıdaki planı alıyorum.

Plan

Planda gösterilen Tablo Kardinaliteleri

  • tblFEStatsPaperHits: 48063400
  • tblFEStatsBrowsers : 339

Bu nedenle taramayı tblFEStatsPaperHits339 kez yapması gerektiğini tahmin ediyor . Her tarama, tblFEStatsBrowsers.BrowserID=tblFEStatsPaperHits.BrowserID AND tblFEStatsPaperHits.BrowserID IS NOT NULLtarama operatörüne itilen korelasyonlu bir yüklemeye sahiptir.

Plan, 339 tam tarama olacağı anlamına gelmiyor. Her bir taramadaki ilk eşleşen satır bulunur bulunmaz bir anti yarı birleştirme operatörünün altında olduğu sürece geri kalanını kısa devre edebilir. Bu düğüm için tahmini alt ağaç maliyeti 1.32603ve tüm planın maliyeti 1.41337.

Hash Join için aşağıdaki planı verir

Hash Katıl

Genel plan, 418.415tek bir tam kümelenmiş endeks taramasının tek başına tblFEStatsPaperHitsmaliyete tabi tutulmasıyla (yuvalanmış halka planından yaklaşık 300 kat daha pahalı) maliyetlendirilir 206.8. Bunu 1.32603daha önce verilen 339 kısmi tarama tahmini ile karşılaştırın (Ortalama kısmi tarama tahmini maliyeti = 0.003911592).

Bu nedenle, her bir kısmi taramanın, tam bir taramanın 53.000 kat daha ucuz olmasından kaynaklandığını gösterir. Maliyetler satır sayımı ile doğrusal olarak ölçeklenirse, bu, ortalama olarak, her bir yinelemede yalnızca 900 satırın eşleşecek bir satır bulması ve kısa devre yapabilmesi için işlem yapması gerektiği varsayımı anlamına gelir.

Bununla birlikte, maliyetlerin bu doğrusal şekilde ölçeklendiğini sanmıyorum. Ayrıca, sabit başlangıç ​​maliyetinin bir kısmını da dahil ettiklerini düşünüyorum. TOPAşağıdaki sorguda çeşitli değerleri denemek

SELECT TOP 147 BrowserID 
FROM [dbo].[tblFEStatsPaperHits] 

147adresine 0.003911592en yakın tahmini alt ağaç maliyetini verir 0.0039113. Her iki yöntemde de maliyetin, her taramanın milyonlarca değil de yüzlerce sıra sırasına göre masanın sadece küçük bir kısmını işlemesi gerektiği varsayımına dayandığı açıktır.

Tam olarak hangi matematiği matematiğe dayandığından emin değilim ve planın geri kalanındaki satır sayısı tahminleriyle gerçekten uyuşmuyor. hiç eşleşen satır bulunmadığı ve tam bir taramanın gerekli olduğu durumlar). Bunun sadece varsayımların varsayımlarının bir miktar aşağıya düştüğü ve iç içe geçmiş döngüleri önemli ölçüde maliyet altında bıraktığı bir durum olduğunu farz ediyorum.


20

Kitabımda 50M satırlık bir tarama bile kabul edilemez ... Her zamanki numaram farklı değerleri ölçmek ve motoru güncel tutmak için delege etmektir.

create view [dbo].[vwFEStatsPaperHitsBrowserID]
with schemabinding
as
select BrowserID, COUNT_BIG(*) as big_count
from [dbo].[tblFEStatsPaperHits]
group by [BrowserID];
go

create unique clustered index [cdxVwFEStatsPaperHitsBrowserID] 
  on [vwFEStatsPaperHitsBrowserID]([BrowserID]);
go

Bu size Tarayıcı Kimliği başına bir satır olan ve 50M satırları taramayı ortadan kaldıran materyalize bir indeks verir. Motor bunu sizin için koruyacak ve QO gönderdiğiniz ifadede 'olduğu gibi' kullanacaktır (herhangi bir ipucu veya sorgu yazmadan).

Dezavantajı elbette çekişmeli. Herhangi bir ekleme veya silme işlemi tblFEStatsPaperHits(ve sanırım ağır ekler içeren bir günlük tablosu), belirli bir Tarayıcı Kimliği'ne erişimi seri hale getirmek zorunda kalacak. Eğer satın almaya istekli iseniz, bunu uygulanabilir hale getirmenin yolları (gecikmeli güncellemeler, 2 aşamalı kayıt vb.) Var.


Seni duyuyorum, bu kadar büyük herhangi bir tarama kesinlikle kabul edilemez. Bu durumda, bir defalık veri temizleme işlemleri içindir, bu yüzden ek indeksler oluşturmamayı tercih ediyorum (ve sistemi kesintiye uğrattığı gibi geçici olarak yapamıyorum). Enerji Verimliliğim yok, ancak bunun bir kereye mahsus olduğuna göre ipuçları iyi olacak. Asıl merakım, QO'nun plandan nasıl kalktığı ile ilgiliydi :) Tablo bir kayıt masası ve ağır kesici uçlar var. Daha sonra tblFEStatsPaperHits içindeki satırları güncellediğinden, gerektiğinde kendim yönetebilmem için ayrı bir zaman uyumsuz günlük kaydı tablosu var.
Mark S. Rasmussen
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.