Akış Ayrımını Zorlama


19

Ben böyle bir tablo var:

CREATE TABLE Updates
(
    UpdateId INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    ObjectId INT NOT NULL
)

Temelde, kimliği giderek artan nesneler için güncellemeleri izlemek.

Bu tablonun tüketicisi, belirli bir sıralamadan UpdateIdbaşlayıp belirli bir sıradan başlayarak 100 farklı nesne tanıtıcısı seçecektir UpdateId. Esasen, kaldığı yerden devam edin ve ardından güncellemeleri sorgulayın.

Ben sadece sorguları yazarak maksimum optimum sorgu planı oluşturmak mümkün oldum çünkü ilginç bir optimizasyon problemi olarak bu saptadığımız gerçekleşmesi Ben endeksler nedeniyle istiyorum, ama yok ne yapacağını garanti ne istiyorum:

SELECT DISTINCT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId

@fromUpdateIdSaklı yordam parametresi nerede .

Bir planla:

SELECT <- TOP <- Hash match (flow distinct, 100 rows touched) <- Index seek

UpdateIdKullanılan endeks arayışı nedeniyle , sonuçlar zaten güzel ve istediğim gibi en düşük en yüksek güncelleme kimliğinden sipariş. Ve bu akış farklı bir plan oluşturuyor , istediğim bu. Ama sipariş açıkçası garantili davranış değil, bu yüzden kullanmak istemiyorum.

Bu hile aynı sorgu planıyla da sonuçlanır (yedek TOP ile olsa da):

WITH ids AS
(
    SELECT ObjectId
    FROM Updates
    WHERE UpdateId > @fromUpdateId
    ORDER BY UpdateId OFFSET 0 ROWS
)
SELECT DISTINCT TOP 100 ObjectId FROM ids

Gerçi, bu gerçekten sipariş garanti edip etmediğinden emin değilim (ve şüpheli değilim).

SQL Server basitleştirmek için yeterince akıllı olacağını umdum bir sorgu, ama çok kötü bir sorgu planı üreten sonuçlanır:

SELECT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId
GROUP BY ObjectId
ORDER BY MIN(UpdateId)

Bir planla:

SELECT <- Top N Sort <- Hash Match aggregate (50,000+ rows touched) <- Index Seek

Ben bir dizin arama UpdateIdve yinelenen s kaldırmak için farklı bir akış ile optimal bir plan oluşturmak için bir yol bulmaya çalışıyorum ObjectId. Herhangi bir fikir?

İsterseniz örnek veriler . Nesnelerin nadiren birden fazla güncellemesi olacak ve 100 satırlık bir kümede neredeyse hiç birden fazla içermemelidir , bu yüzden bilmediğim daha iyi bir şey olmadığı sürece bir akıştan sonrayım ? Ancak, tek bir ObjectIdtabloda 100'den fazla satır olmayacağının garantisi yoktur . Tablo 1.000.000'dan fazla satıra sahiptir ve hızla büyümesi beklenmektedir.

Bu kullanıcının uygun bir sonraki bulmak için başka bir yolu olduğunu varsayalım @fromUpdateId. Bu sorguda döndürmenize gerek yok.

Yanıtlar:


15

SQL Match optimizer , ihtiyaç duyduğunuz garantiyle peşinde olduğunuz yürütme planını üretemez, çünkü Hash Match Flow Distinct operatörü siparişi korumaz.

Gerçi, bu gerçekten sipariş garanti edip etmediğinden emin değilim (ve şüpheli değilim).

Sen olabilir gözlemlemek birçok durumda sipariş korunması, ancak bu uygulama detayıdır; garanti yoktur, bu yüzden ona güvenemezsiniz. Her zaman olduğu gibi, sunum sırası yalnızca üst düzey bir ORDER BYmadde ile garanti edilebilir .

Misal

Aşağıdaki komut dosyası Karma Eşleşme Akış Ayrımı'nın sırayı korumadığını gösterir. Söz konusu tabloyu, her iki sütunda 1-50.000 eşleşen sayılarla ayarlar:

IF OBJECT_ID(N'dbo.Updates', N'U') IS NOT NULL
    DROP TABLE dbo.Updates;
GO
CREATE TABLE Updates
(
    UpdateId INT NOT NULL IDENTITY(1,1),
    ObjectId INT NOT NULL,

    CONSTRAINT PK_Updates_UpdateId PRIMARY KEY (UpdateId)
);
GO
INSERT dbo.Updates (ObjectId)
SELECT TOP (50000)
    ObjectId =
        ROW_NUMBER() OVER (
            ORDER BY C1.[object_id]) 
FROM sys.columns AS C1
CROSS JOIN sys.columns AS C2
ORDER BY
    ObjectId;

Test sorgusu:

DECLARE @Rows bigint = 50000;

-- Optimized for 1 row, but will be 50,000 when executed
SELECT DISTINCT TOP (@Rows)
    U.ObjectId 
FROM dbo.Updates AS U
WHERE 
    U.UpdateId > 0
OPTION (OPTIMIZE FOR (@Rows = 1));

Tahmini plan bir endeks arayışı ve akışı farklıdır:

Tahmini plan

Çıktı kesinlikle şu şekilde başlamalı:

Sonuçların başlangıcı

... ancak daha aşağı değerler 'eksik' olmaya başlar:

Desen parçalanıyor

... ve sonunda:

Kaos patlak verir

Bu özel durumdaki açıklama, hash operatörünün döktüğü şudur:

İcra sonrası plan

Bir bölüm döküldüğünde, aynı bölüme hash eden tüm satırlar da dökülür. Dökülen bölümler daha sonra işlenir ve karşılaşılan farklı değerlerin alındıkları sırada derhal yayılacağı beklentisini ortadan kaldırır.


Özyineleme veya imleç kullanma gibi istediğiniz sonucu elde etmek için etkili bir sorgu yazmanın birçok yolu vardır. Ancak, Hash Match Flow Distinct kullanılarak yapılamaz .


11

Bu yanıttan memnun değilim çünkü doğru olduğu garanti edilen sonuçlarla birlikte akıştan farklı bir operatör almayı başaramadım. Bununla birlikte, doğru sonuçlarla birlikte iyi performans alması gereken bir alternatifim var. Ne yazık ki, tabloda kümelenmemiş bir dizin oluşturulmasını gerektirir.

Bu soruna, yapabileceğim sütunların bir kombinasyonunu düşünmeye ORDER BYve DISTINCTonlara uyguladıktan sonra doğru sonuçları elde etmeye çalışarak yaklaştım . UpdateIdİle ObjectIdbirlikte başına minimum değer ObjectIdböyle bir kombinasyondur. Ancak, doğrudan asgari istemek UpdateId, tablodaki tüm satırların okunmasıyla sonuçlanıyor gibi görünmektedir. Bunun yerine dolaylı olarak UpdateIdmasaya başka bir birleştirme ile asgari değerini isteyebiliriz . Fikir, Updatestabloyu sırayla taramak, UpdateIdo satırın minimum değeri olmayan satırları atmak ObjectIdve ilk 100 satırı tutmaktır. Veri dağıtımı açıklamanıza dayanarak çok fazla satır atmamıza gerek kalmaz.

Veri hazırlama için, her bir ObjectId için 2 satır içeren bir tabloya 1 milyon satır koydum:

INSERT INTO Updates WITH (TABLOCK)
SELECT t.RN / 2
FROM 
(
    SELECT TOP 1000000 -1 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) t;

CREATE INDEX IX On Updates (Objectid, UpdateId);

Kümelenmemiş dizin açık Objectidve UpdateIdönemlidir. Minimum UpdateIdbaşına olmayan satırları verimli bir şekilde atmamızı sağlar Objectid. Yukarıdaki açıklamayla eşleşen bir sorgu yazmanın birçok yolu vardır. İşte böyle bir yol NOT EXISTS:

DECLARE @fromUpdateId INT = 9999;
SELECT ObjectId
FROM (
    SELECT DISTINCT TOP 100 u1.UpdateId, u1.ObjectId
    FROM Updates u1
    WHERE UpdateId > @fromUpdateId
    AND NOT EXISTS (
        SELECT 1
        FROM Updates u2
        WHERE u2.UpdateId > @fromUpdateId
        AND u1.ObjectId = u2.ObjectId
        AND u2.UpdateId < u1.UpdateId
    )
    ORDER BY u1.UpdateId, u1.ObjectId
) t;

İşte sorgu planının bir resmi :

sorgu planı

En iyi durumda, SQL Server kümelenmemiş dizine karşı yalnızca 100 dizin arar. Çok şanssızlaşmayı simüle etmek için, sorguyu ilk 5000 satırı istemciye döndürecek şekilde değiştirdim. Bu 9999 endeksi aramasıyla sonuçlandı, bu yüzden farklı başına ortalama 100 satır almak gibi ObjectId. İşte çıktı SET STATISTICS IO, TIME ON:

'Güncellemeler' tablosu. Tarama sayısı 10000, mantıksal okuma 31900, fiziksel okumalar 0

SQL Server Yürütme Süreleri: CPU süresi = 31 ms, geçen süre = 42 ms.


9

Soruyu seviyorum - Flow Distinct en sevdiğim operatörlerden biri.

Şimdi, garanti sorun. FD operatörünün sıralarını Ara operatöründen sıralı bir şekilde çekmesini düşündüğünüzde, her satırı benzersiz olduğunu belirlediği gibi ürettiğinizde, bu size satırları doğru sırada verecektir. Ancak, FD'nin bir kerede tek bir satırı işlemediği bazı senaryolar olup olmadığını bilmek zor.

Teorik olarak, FD, Arama'dan 100 satır isteyebilir ve bunları ihtiyaç duydukları sırada üretebilir.

Sorgu ipuçları OPTION (FAST 1, MAXDOP 1)yardımcı olabilir, çünkü Arama işlecinden gerekenden daha fazla satır almaktan kaçınır. Yine de bir garanti mi? Pek değil. Yine de bir seferde bir satır sayfa çekmeye veya bunun gibi bir şeye karar verebilir.

Bence OPTION (FAST 1, MAXDOP 1), OFFSETsürümünüz sipariş hakkında size çok güven verecektir , ancak bu bir garanti değildir.


Anladığım kadarıyla sorun, Flow Distinct operatörünün diske dökülebilen bir karma tablosu kullanmasıdır. Bir dökülme olduğunda, hala RAM'deki kısım kullanılarak işlenebilen satırlar hemen işlenir, ancak dökülen veriler diskten okunana kadar diğer satırlar işlenmez. Söyleyebileceğim kadarıyla, hash tablosu kullanan herhangi bir operatörün (Hash Join gibi) dökülme davranışı nedeniyle düzeni koruması garanti edilmez.
sam.bishop

Doğru. Paul White'ın cevabına bakınız.
Rob Farley
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.