Persisted Computed sütununda dizin, hesaplanan ifadede sütunları almak için anahtar arama gerektiriyor


24

Birleştirilmiş bir hesaplanmış sütun var, basitçe birleştirilmiş sütunlardan oluşan bir masada.

CREATE TABLE dbo.T 
(   
    ID INT IDENTITY(1, 1) NOT NULL CONSTRAINT PK_T_ID PRIMARY KEY,
    A VARCHAR(20) NOT NULL,
    B VARCHAR(20) NOT NULL,
    C VARCHAR(20) NOT NULL,
    D DATE NULL,
    E VARCHAR(20) NULL,
    Comp AS A + '-' + B + '-' + C PERSISTED NOT NULL 
);

Bu Compbenzersiz değildir ve D, her bir kombinasyonun tarihinden itibaren geçerlidir A, B, C, bu nedenle her birinin bitiş tarihini almak için aşağıdaki sorguyu kullanıyorum A, B, C(temel olarak Comp'in aynı değeri için bir sonraki başlangıç ​​tarihi):

SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 t2.D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY t2.D
            )
FROM    dbo.T t1
WHERE   t1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY t1.Comp;

Daha sonra bu sorgulamaya yardımcı olmak için hesaplanan sütuna bir dizin ekledim:

CREATE NONCLUSTERED INDEX IX_T_Comp_D ON dbo.T (Comp, D) WHERE D IS NOT NULL;

Ancak sorgu planı beni şaşırttı. Bunu söyleyen bir maddeye sahip olduğumdan D IS NOT NULLve bunu Compsıraladığımdan ve hesaplanan sütundaki dizinin t1 ve t2'yi taramak için kullanılabileceğini, ancak dizinin dışındaki herhangi bir sütuna atıfta bulunmadığımı düşünürdüm, ancak kümelenmiş bir dizin gördüm tarayın.

görüntü tanımını buraya girin

Bu yüzden, daha iyi bir plan yapıp yapmadığını görmek için bu endeksin kullanımını zorladım:

SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 t2.D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY t2.D
            )
FROM    dbo.T t1 WITH (INDEX (IX_T_Comp_D))
WHERE   t1.D IS NOT NULL
ORDER BY t1.Comp;

Bu planı veren

görüntü tanımını buraya girin

Bu, bir Anahtar aramanın kullanıldığını gösterir; bunların ayrıntıları:

görüntü tanımını buraya girin

Şimdi, SQL-Server belgelerine göre:

Sütun, CREATE TABLE veya ALTER TABLE ifadesinde PERSISTED olarak işaretlenmişse, deterministik ancak kesin olmayan bir ifadeyle tanımlanan bir hesaplanmış sütun üzerinde bir dizin oluşturabilirsiniz. Bu, Veritabanı Motorunun hesaplanan değerleri tabloda sakladığı ve hesaplanan sütunun bağlı olduğu diğer sütunlar güncellendiğinde bunları güncellediği anlamına gelir. Veritabanı Altyapısı, bu kalıcı değerleri, sütunda bir dizin oluşturduğunda ve dizine bir sorguda başvurulduğunda kullanır. Bu seçenek, Database Engine, hesaplanan sütun ifadelerini döndüren bir fonksiyonun, özellikle de .NET Framework'te oluşturulan bir CLR fonksiyonunun hem deterministik hem de kesin olup olmadığını doğrulukla kanıtlayamadığında hesaplanan bir sütunda bir dizin oluşturmanıza olanak sağlar.

Öyleyse, dokümanlar "Veritabanı Altyapısı tablodaki hesaplanan değerleri depolar" dediğinde ve değer de dizinimde saklanıyorsa, başvuruda bulunmadıklarında neden A, B ve C almak için bir Anahtar Arama gerekli? hiç sorgu? Comp hesaplamak için kullanıldığını sanıyorum, ama neden? Ayrıca, sorgu neden dizini kullanabilir t2, ancak kullanmıyor t1?

SQL Fiddle'da Sorgular ve DDL

NB SQL Server 2008'i etiketledim, çünkü bu ana sorunumun açık olduğu sürüm, ancak 2012'de de aynı davranışı alıyorum.

Yanıtlar:


20

Sorguda hiç referans alınmadıklarında A, B ve C'yi almak için neden Anahtar Arama gerekli? Comp hesaplamak için kullanıldığını sanıyorum, ama neden?

Sütunlar A, B, and C edilir sorgu planında başvurulan - onlar üzerinde aramaya tarafından kullanılmaktadır T2.

Ayrıca, sorgu neden t2'deki dizini kullanabilir ama t1'de kullanılamaz?

Doktor, kümelenmiş dizini taramanın, filtrelenmiş kümelenmemiş dizini taramaktan ve daha sonra A, B ve C sütunlarının değerlerini almak için bir arama yapmaktan daha ucuz olduğuna karar verdi.

açıklama

Asıl soru, optimizer'ın neden endeks aramak için A, B ve C alma ihtiyacı hissettiğidir. CompSütunu kümelenmemiş bir dizin taraması kullanarak okumasını ve ardından ilk 1 kaydı bulmak için aynı dizinde (diğer ad T2) bir arama yapmasını bekleriz .

Sorgu en iyi duruma getiricisi, çeşitli sorgu planlarının maliyetlerini değerlendirme şansı vermek için en iyileştirme başlamadan önce hesaplanan sütun referanslarını genişletir. Bazı sorgular için, hesaplanmış bir sütun tanımının genişletilmesi, optimize edicinin daha verimli planlar bulmasını sağlar.

İyileştirici ilişkili bir alt sorgula karşılaştığında, nedenini daha kolay bulduğu bir forma 'açmaya' çalışır. Daha etkili bir basitleştirme bulamazsa, ilişkili alt sorguyu bir başvuru olarak yeniden yazmaya başvurur (ilişkili bir birleştirme):

Yeniden yazma uygula

Bu böyle olur, uygulamanın kaldırılması mantıksal sorgu ağacını proje normalleştirmesiyle iyi çalışmayan bir forma sokar (diğer ifadelerin yanı sıra hesaplanan sütunlarla genel ifadeleri eşleştiren bir sonraki aşama).

Sizin durumunuzda, sorgunun yazılma şekli, optimize edicinin iç detaylarıyla etkileşime girerek, genişletilmiş ifade tanımının hesaplanan sütuna geri dönmemesini sağlar ve sonuçta A, B, and Chesaplanan sütun yerine sütunlara başvuran bir arama ile sonuçlanır Comp. Bu kök nedenidir.

Geçici çözüm

Bu yan etkiyi gidermek için bir fikir, sorguyu elle geçerli olarak yazmaktır:

SELECT
    T1.ID,
    T1.Comp,
    T1.D,
    CA.D2
FROM dbo.T AS T1
CROSS APPLY
(  
    SELECT TOP (1)
        D2 = T2.D
    FROM dbo.T AS T2
    WHERE
        T2.Comp = T1.Comp
        AND T2.D > T1.D
    ORDER BY
        T2.D ASC
) AS CA
WHERE
    T1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY
    T1.Comp;

Ne yazık ki, bu sorgu da ümit edeceğimiz gibi filtrelenmiş dizini kullanmayacak. Uygulamanın Diçindeki sütunda eşitsizlik denemesi reddedilir NULLs, bu nedenle görünüşte fazlalık belirleme WHERE T1.D IS NOT NULLuzağa optimize edilir.

Bu açık belirtme olmadan, filtre uygulanmış dizin eşleştirme mantığı, filtre uygulanmış dizini kullanamayacağına karar verir. Bu ikinci yan etkinin etrafında çalışmanın birkaç yolu vardır, ancak en kolay olanı muhtemelen dış bir uygulamaya uygulanan haçı değiştirmektir (ilgili alt sorguda daha önce gerçekleştirilen iyileştiricinin yeniden yazma mantığını yansıtır):

SELECT
    T1.ID,
    T1.Comp,
    T1.D,
    CA.D2
FROM dbo.T AS T1
OUTER APPLY
(  
    SELECT TOP (1)
        D2 = T2.D
    FROM dbo.T AS T2
    WHERE
        T2.Comp = T1.Comp
        AND T2.D > T1.D
    ORDER BY
        T2.D ASC
) AS CA
WHERE
    T1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY
    T1.Comp;

Şimdi, optimize edicinin, yeniden yazma uygulamasının kendisini kullanması gerekmez (bu nedenle hesaplanan sütun eşlemesi beklendiği gibi çalışır) ve yüklem de en iyi duruma getirilmez, böylece filtre uygulanmış dizin her iki veri erişim işlemi için kullanılabilir ve arama Compsütunu kullanır. iki tarafta da:

Dış uygulama planı

Bu genellikle INCLUDEdfiltrelenmiş dizinde sütunlar olarak A, B ve C'nin eklenmesinde tercih edilir , çünkü sorunun kök nedenini giderir ve dizinin gereksiz yere genişletilmesini gerektirmez.

Kalıcı hesaplanmış sütunlar

Yan not olarak PERSISTED, tanımını bir CHECKsınırlamada tekrar etmeyi sakıncası yoksa , hesaplanan sütunu işaretlemek gerekli değildir :

CREATE TABLE dbo.T 
(   
    ID integer IDENTITY(1, 1) NOT NULL,
    A varchar(20) NOT NULL,
    B varchar(20) NOT NULL,
    C varchar(20) NOT NULL,
    D date NULL,
    E varchar(20) NULL,
    Comp AS A + '-' + B + '-' + C,

    CONSTRAINT CK_T_Comp_NotNull
        CHECK (A + '-' + B + '-' + C IS NOT NULL),

    CONSTRAINT PK_T_ID 
        PRIMARY KEY (ID)
);

CREATE NONCLUSTERED INDEX IX_T_Comp_D
ON dbo.T (Comp, D) 
WHERE D IS NOT NULL;

Hesaplanan sütunun, yalnızca PERSISTEDbir NOT NULLkısıtlama kullanmak veya bir kısıtlamadaki Compsütuna doğrudan (tanımını tekrarlamak yerine) başvurmak istiyorsanız bu durumda olması gerekir CHECK.


2
+1 BTW İlgi çekici bulabileceğiniz (ya da bulamayabileceğiniz) bakarken başka bir gereksiz arama vakası ile karşılaştım. SQL Fiddle .
Martin Smith

@ MartinSmith Evet, bu ilginç. FOJNtoLSJNandLASJNUmudumuz gibi çalışmayan şeylerle sonuçlanan ve bazı plan türlerinde (örn. İmleçler) yararlı olan ancak burada gerekmeyen önemsiz bırakan başka bir genel kural yeniden yazar ( ).
Paul Beyaz GoFundMonica diyor

Ah Chkchecksum! Teşekkürler bundan emin değildim. Başlangıçta, kontrol kısıtlamaları ile ilgisi olabileceğini düşünüyordum.
Martin Smith

6

Bu, test verilerinizin yapay yapısından dolayı biraz eşlik edebilse de, SQL 2012 dediğiniz gibi bir yeniden yazma denedim:

SELECT  ID,
        Comp,
        D,
        D2 = LEAD(D) OVER(PARTITION BY COMP ORDER BY D)
FROM    dbo.T 
WHERE   D IS NOT NULL
ORDER BY Comp;

Bu, endeksinizi kullanarak ve diğer seçeneklerden (ve test verileriniz için aynı sonuçları) önemli ölçüde daha düşük okuma oranıyla güzel bir düşük maliyetli plan sağladı.

Dört seçenek için Explorer maliyetlerini planlayın: Orijinal;  ipucu ile orijinal;  dış uygulama ve Kurşun

Gerçek verilerinizin daha karmaşık olduğundan şüpheliyim, bu nedenle bu sorgunun sizinkinden tamamen farklı davrandığı bazı senaryolar olabilir, ancak bazen yeni özelliklerin gerçek bir fark yaratabileceğini gösteriyor.

Daha çeşitli verilerle denemeler yaptım ve eşleşecek bazı senaryolar buldum, bazıları ise:

--Example 1: results matched
TRUNCATE TABLE dbo.t

-- Generate some more interesting test data
;WITH cte AS
(
SELECT TOP 1000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT T (A, B, C, D)
SELECT  'A' + CAST( a.rn AS VARCHAR(5) ),
        'B' + CAST( a.rn AS VARCHAR(5) ),
        'C' + CAST( a.rn AS VARCHAR(5) ),
        DATEADD(DAY, a.rn + b.rn, '1 Jan 2013')
FROM cte a
    CROSS JOIN cte b
WHERE a.rn % 3 = 0
 AND b.rn % 5 = 0
ORDER BY 1, 2, 3
GO


-- Original query
SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY D
            )
INTO #tmp1
FROM    dbo.T t1 
WHERE   t1.D IS NOT NULL
ORDER BY t1.Comp;
GO

SELECT  ID,
        Comp,
        D,
        D2 = LEAD(D) OVER(PARTITION BY COMP ORDER BY D)
INTO #tmp2
FROM    dbo.T 
WHERE   D IS NOT NULL
ORDER BY Comp;
GO


-- Checks ...
SELECT * FROM #tmp1
EXCEPT
SELECT * FROM #tmp2

SELECT * FROM #tmp2
EXCEPT
SELECT * FROM #tmp1


Example 2: results did not match
TRUNCATE TABLE dbo.t

-- Generate some more interesting test data
;WITH cte AS
(
SELECT TOP 1000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT T (A, B, C, D)
SELECT  'A' + CAST( a.rn AS VARCHAR(5) ),
        'B' + CAST( a.rn AS VARCHAR(5) ),
        'C' + CAST( a.rn AS VARCHAR(5) ),
        DATEADD(DAY, a.rn, '1 Jan 2013')
FROM cte a

-- Add some more data
INSERT dbo.T (A, B, C, D)
SELECT A, B, C, D 
FROM dbo.T
WHERE DAY(D) In ( 3, 7, 9 )


INSERT dbo.T (A, B, C, D)
SELECT A, B, C, DATEADD( day, 1, D )
FROM dbo.T
WHERE DAY(D) In ( 12, 13, 17 )


SELECT * FROM #tmp1
EXCEPT
SELECT * FROM #tmp2

SELECT * FROM #tmp2
EXCEPT
SELECT * FROM #tmp1

SELECT * FROM #tmp2
INTERSECT
SELECT * FROM #tmp1


select * from #tmp1
where comp = 'A2-B2-C2'

select * from #tmp2
where comp = 'A2-B2-C2'

1
Peki endeksi kullanır, ancak bir noktaya kadar. Eğer compbir hesaplanan sütun değil sen tür görmüyorum.
Martin Smith

Teşekkürler. Asıl senaryom çok daha karmaşık değil ve LEADyerel olarak ifade ettiğim 2012 ekspresinde istediğim gibi çalıştı. Ne yazık ki, bu küçük sıkıntı bana henüz üretim sunucularını yükseltmek için yeterince iyi bir neden değildi ...
GarethD

-1

Aynı işlemleri yapmaya çalıştığımda, bir başka sonuç daha çıktı. Öncelikle, indekssiz tablo için yürütme planım aşağıdaki gibidir:görüntü tanımını buraya girin

Kümelenmiş Dizin Taraması'ndan (t2) görebileceğimiz gibi, belirtim döndürülmesi gereken satırları belirlemek için kullanılır (durumdan dolayı):

görüntü tanımını buraya girin

İndeks eklendiğinde, WITH operatörü tarafından tanımlandı mı yoksa hayır mı olduğu bir konu değil, yürütme planı aşağıdaki gibi oldu:

görüntü tanımını buraya girin

Gördüğümüz gibi, Kümelenmiş Dizin Taraması, Dizin Taraması ile değiştirildi. Yukarıda gördüğümüz gibi, SQL Server iç içe geçmiş sorgunun eşleşmesini gerçekleştirmek için hesaplanan sütunun kaynak sütunlarını kullanır. Kümelenmiş indeks taraması sırasında tüm bu değerler aynı anda elde edilebilir (ek işlem gerekmez). Dizin eklendiğinde, gerekli satırların tablodan (ana seçimde) filtrelenmesi, dizine göre gerçekleştirilir, ancak hesaplanan sütun için kaynak sütunların değerlerinin comphala elde edilmesi gerekir (en son işlem İç İçe Döngü) .

görüntü tanımını buraya girin

Bu nedenle, Anahtar Arama işlemi kullanılır - hesaplananın kaynak sütunlarının verilerini almak için kullanılır.

Ps sql Server'da bir hata gibi görünüyor.

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.