“MEVCUT (…) VEYA MEVCUT (…)” hükümlerinin sıralaması


11

İki şeyden birinin varlığını test eden bir sorgu sınıfım var. Şeklinde

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM ...)
  OR EXISTS (SELECT 1 FROM ...)
THEN 1 ELSE 0 END;

Gerçek ifade C'de oluşturulur ve ODBC bağlantısı üzerinden geçici bir sorgu olarak yürütülür.

Son zamanlarda, çoğu durumda ikinci SELECT'in muhtemelen ilk SELECT'ten daha hızlı olacağı ve iki EXISTS deyiminin sırasının değiştirilmesinin, henüz oluşturduğumuz en az bir kötü niyetli test durumunda ciddi bir hızlanmaya neden olduğu ortaya çıktı.

Yapılması gereken şey sadece devam edip iki cümleyi değiştirmektir, ancak SQL Server'ı daha iyi bilen birinin buna ağırlık verip vermeyeceğini görmek istedim. Tesadüf ve bir "uygulama detayı" na güveniyor gibi hissediyorum.

(SQL Server daha akıllı olsaydı, her iki EXISTS yan tümcesini paralel olarak yürütür ve hangisinin ilk kısa devreyi diğerini tamamladığını sağlar.)

Böyle bir sorgunun çalışma süresini sürekli olarak iyileştirmek için SQL Server almanın daha iyi bir yolu var mı?

Güncelleme

Zamanım ve soruma gösterdiğiniz ilgi için teşekkür ederim. Asıl sorgu planları hakkında soru beklemiyordum, ama bunları paylaşmak istiyorum.

Bu, SQL Server 2008R2 ve üstünü destekleyen bir yazılım bileşeni içindir. Verilerin şekli, yapılandırmaya ve kullanıma bağlı olarak oldukça farklı olabilir. İş arkadaşım sorguda bu değişikliği yapmayı düşündü çünkü (örnekte) dbf_1162761$z$rv$1257927703tablo her zaman tablodaki satır sayısından daha fazla veya eşit olacaktır dbf_1162761$z$dd$1257927703- bazen önemli ölçüde daha fazla (büyüklük sıraları).

İşte bahsettiğim küfürlü dava. İlk sorgu yavaş olanıdır ve yaklaşık 20 saniye sürer. İkinci sorgu bir anda tamamlanır.

Değeri ne olursa olsun, yakın zamanda "BİLİNMEYEN OPTİMİZE ET" biti de eklendi çünkü parametre koklaması bazı durumları çöpe atıyordu.

Orijinal sorgu:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$rv$1257927703 rv INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=rv.txid WHERE tx.generation BETWEEN 1500 AND 2502)
  OR EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$dd$1257927703 dd INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=dd.txid WHERE tx.generation BETWEEN 1500 AND 2502)
THEN 1 ELSE 0 END
OPTION (OPTIMIZE FOR UNKNOWN)

Asıl plan:

|--Compute Scalar(DEFINE:([Expr1006]=CASE WHEN [Expr1007] THEN (1) ELSE (0) END))
     |--Nested Loops(Left Semi Join, DEFINE:([Expr1007] = [PROBE VALUE]))
          |--Constant Scan
          |--Concatenation
               |--Nested Loops(Inner Join, WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]))
               |    |--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[PK__dbf_1162__97770A2F62EEAE79] AS [rv]), WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]>(0)))
               |    |--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[gendex] AS [tx]), SEEK:([tx].[generation] >= (1500) AND [tx].[generation] <= (2502)) ORDERED FORWARD)
               |--Nested Loops(Inner Join, OUTER REFERENCES:([tx].[txid]))
                    |--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[PK__dbf_1162__E3BA953EC2197789] AS [tx]),  WHERE:([scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]>=(1500) AND [scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]<=(2502)) ORDERED FORWARD)
                    |--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[n$dbf_1162761$z$dd$txid$1257927703] AS [dd]), SEEK:([dd].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]),  WHERE:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[txid] as [dd].[txid]>(0)) ORDERED FORWARD)

Sabit sorgu:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$dd$1257927703 dd INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=dd.txid WHERE tx.generation BETWEEN 1500 AND 2502)
  OR EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$rv$1257927703 rv INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=rv.txid WHERE tx.generation BETWEEN 1500 AND 2502)
THEN 1 ELSE 0 END
OPTION (OPTIMIZE FOR UNKNOWN)

Sabit plan:

|--Compute Scalar(DEFINE:([Expr1006]=CASE WHEN [Expr1007] THEN (1) ELSE (0) END))
     |--Nested Loops(Left Semi Join, DEFINE:([Expr1007] = [PROBE VALUE]))
          |--Constant Scan
          |--Concatenation
               |--Nested Loops(Inner Join, OUTER REFERENCES:([tx].[txid]))
               |    |--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[PK__dbf_1162__E3BA953EC2197789] AS [tx]),  WHERE:([scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]>=(1500) AND [scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]<=(2502)) ORDERED FORWARD)
               |    |--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[n$dbf_1162761$z$dd$txid$1257927703] AS [dd]), SEEK:([dd].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]),  WHERE:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[txid] as [dd].[txid]>(0)) ORDERED FORWARD)
               |--Nested Loops(Inner Join, WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]))
                    |--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[PK__dbf_1162__97770A2F62EEAE79] AS [rv]), WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]>(0)))
                    |--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[gendex] AS [tx]), SEEK:([tx].[generation] >= (1500) AND [tx].[generation] <= (2502)) ORDERED FORWARD)

Yanıtlar:


11

Genel bir kural olarak, SQL Server bir CASEifadenin bölümlerini sırayla yürütür, ancak ORkoşulları yeniden sıralamak ücretsizdir . Bazı sorgular için, bir WHENifadedeki ifadelerin sırasını değiştirerek tutarlı bir şekilde daha iyi performans elde edebilirsiniz CASE. Bazen bir ORifadedeki koşulların sırasını değiştirirken daha iyi performans elde edebilirsiniz , ancak bu davranış garanti edilmez.

Basit bir örnekle üzerinde yürümek muhtemelen en iyisidir. SQL Server 2016'ya karşı test ediyorum, bu yüzden makinenizde aynı sonuçları almamanız mümkün, ancak bildiğim kadarıyla aynı ilkeler geçerlidir. İlk olarak, bir kümelenmiş dizin ve bir yığın olarak iki tabloya 1 ila 1000000 arasında bir milyon tamsayı koyacağım:

CREATE TABLE dbo.X_HEAP (ID INT NOT NULL, FLUFF VARCHAR(100));

INSERT INTO dbo.X_HEAP  WITH (TABLOCK)
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);

CREATE TABLE dbo.X_CI (ID INT NOT NULL, FLUFF VARCHAR(100), PRIMARY KEY (ID));

INSERT INTO dbo.X_CI  WITH (TABLOCK)
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);

Aşağıdaki sorguyu düşünün:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000)
  OR EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000)
THEN 1 ELSE 0 END;

Alt sorguyu değerlendirmenin , özellikle eşleşen bir satır olmadığında X_CI, alt sorgundan çok daha ucuz olacağını biliyoruz X_HEAP. Eşleşen bir satır yoksa, kümelenmiş bir dizin içeren tabloya karşı yalnızca birkaç mantıksal okuma yapmamız gerekir. Ancak, eşleşen bir satır olmadığını bilmek için öbekteki tüm satırları taramamız gerekir. Optimizer bunu da biliyor. Genel olarak konuşursak, bir satırı aramak için kümelenmiş bir dizin kullanmak tablo taramaya kıyasla çok ucuzdur.

Bu örnek veri için böyle bir sorgu yazmak istiyorum:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000) THEN 1 
  WHEN EXISTS (SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000) THEN 1 
ELSE 0 END;

Bu etkin bir şekilde SQL Server'ı alt sorguyu önce kümelenmiş bir dizinle tabloya karşı çalıştırmaya zorlar. İşte sonuçları SET STATISTICS IO, TIME ON:

Tablo 'X_CI'. Tarama sayısı 0, mantıksal okuma 3, fiziksel okuma 0

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

Sorgu planına bakıldığında, etiket 1'deki arama, etiket 2'deki taramadan başka bir veri döndürürse ve gerçekleşmezse:

iyi sorgu

Aşağıdaki sorgu çok daha az verimlidir:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000) THEN 1 
  WHEN EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000) THEN 1 
ELSE 0 END
OPTION (MAXDOP 1);

Sorgu planına baktığımızda, etiket 2'deki taramanın her zaman gerçekleştiğini görüyoruz. Bir satır bulunursa, etiket 1'deki arama atlanır. İstediğimiz sıra bu değil:

hatalı sorgu planı

Performans bunun sonucunu veriyor:

Tablo 'X_HEAP'. Tarama sayısı 1, mantıksal okuma 7247

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

Orijinal sorguya geri dönersek, bu sorgu için arama ve tarama performansı için iyi sırayla değerlendirilir:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000)
  OR EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000)
THEN 1 ELSE 0 END;

Ve bu sorguda ters sırayla değerlendirilirler:

SELECT CASE
  WHEN EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000)
  OR EXISTS (SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000)
THEN 1 ELSE 0 END;

Ancak, önceki sorgu çiftinin aksine, SQL Server sorgu iyileştiricisini birbiri ardına değerlendirmeye zorlayan hiçbir şey yoktur. Önemli bir şey için bu davranışa güvenmemelisiniz.

Sonuç olarak, bir alt sorgunun diğerinden önce değerlendirilmesi gerekiyorsa, CASEsıralamayı zorlamak için bir ifade veya başka bir yöntem kullanın. Aksi takdirde, alt sorguları ORistediğiniz durumda sipariş etmekte özgürsünüz , ancak optimize edicinin bunları yazılı olarak uygulayacağına dair bir garanti olmadığını bilin.

Zeyilname:

Doğal bir takip sorusu, SQL Server'ın hangi sorgunun daha ucuz olduğuna karar vermesini ve ilk önce bu sorguyu yürütmesini istiyorsanız ne yapabilirsiniz? Şimdiye kadar tüm yöntemler, bazıları için garanti edilen davranış olmasa bile, sorgunun yazıldığı sırada SQL Server tarafından uygulanmış görünmektedir.

Basit demo tabloları için çalışıyor gibi görünen bir seçenek:

SELECT CASE
  WHEN EXISTS (
    SELECT 1
    FROM (
        SELECT TOP 2 1 t
        FROM 
        (
            SELECT 1 ID

            UNION ALL

            SELECT TOP 1 ID 
            FROM dbo.X_HEAP 
            WHERE ID = 50000 
        ) h
        CROSS JOIN
        (
            SELECT 1 ID

            UNION ALL

            SELECT TOP 1 ID 
            FROM dbo.X_CI
            WHERE ID = 50000
        ) ci
    ) cnt
    HAVING COUNT(*) = 2
)
THEN 1 ELSE 0 END;

Burada bir db keman demosu bulabilirsiniz . Türetilmiş tabloların sırasını değiştirmek sorgu planını değiştirmez. Her iki sorguda da X_HEAPtabloya dokunulmaz. Başka bir deyişle, sorgu iyileştirici önce daha ucuz sorguyu yürütüyor gibi görünüyor. Üretimde böyle bir şey kullanmanızı tavsiye edemem, bu yüzden çoğunlukla merak değeri için burada. Aynı şeyi başarmanın çok daha kolay bir yolu olabilir.


4
Ya CASE WHEN EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000 UNION ALL SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000) THEN 1 ELSE 0 ENDda bir alternatif olabilir, ancak yine de hangi sorgunun daha hızlı olduğuna ve bu soruyu ilk sıraya koymaya manuel olarak dayanır. Bunu ifade etmek için bir yolu olup olmadığını emin değilim böylece SQL Server otomatik olarak yeniden ucuz bir ilk değerlendirilir.
Martin Smith
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.