Bir arama beklediğim halde tarama yapıyorum


9

Bir SELECTdeyimi optimize etmek gerekir, ancak SQL Server her zaman bir arama yerine bir dizin taraması yapar. Tabii ki, saklı bir yordamda olan sorgu budur:

CREATE PROCEDURE dbo.something
  @Status INT = NULL,
  @IsUserGotAnActiveDirectoryUser BIT = NULL    
AS

    SELECT [IdNumber], [Code], [Status], [Sex], 
           [FirstName], [LastName], [Profession], 
           [BirthDate], [HireDate], [ActiveDirectoryUser]
    FROM Employee
    WHERE (@Status IS NULL OR [Status] = @Status)
    AND 
    (
      @IsUserGotAnActiveDirectoryUser IS NULL 
      OR 
      (
        @IsUserGotAnActiveDirectoryUser IS NOT NULL AND       
        (
          @IsUserGotAnActiveDirectoryUser = 1 AND ActiveDirectoryUser <> ''
        )
        OR
        (
          @IsUserGotAnActiveDirectoryUser = 0 AND ActiveDirectoryUser = ''
        )
      )
    )

Ve bu dizin:

CREATE INDEX not_relevent ON dbo.Employee
(
    [Status] DESC,
    [ActiveDirectoryUser] ASC
)
INCLUDE (...all the other columns in the table...); 

Plan:

Resmi planla

SQL Server neden bir tarama seçti? Nasıl düzeltebilirim?

Sütun tanımları:

[Status] int NOT NULL
[ActiveDirectoryUser] VARCHAR(50) NOT NULL

Durum parametreleri şunlar olabilir:

NULL: all status,
1: Status= 1 (Active employees)
2: Status = 2 (Inactive employees)

IsUserGotAnActiveDirectoryUser olabilir:

NULL: All employees
0: ActiveDirectoryUser is empty for that employee
1: ActiveDirectoryUser  got a valid value (not null and not empty)

Gerçek yürütme planını bir yere gönderebilir misiniz (bunun resmi değil, XML biçiminde .sqlplan dosyası)? Benim tahminim, prosedürü değiştirmişsiniz ama aslında ifade düzeyinde yeni bir derleme almamışsınızdır. Sorgunun bazı metnini değiştirebilir ( şema önekini tablo adına eklemek gibi ) ve sonra için geçerli bir değer iletebilir @Statusmisiniz?
Aaron Bertrand

1
Ayrıca indeks tanımı soruyu akla getiriyor - anahtar neden açık Status DESC? Kaç değer vardır Status, bunlar nedir (sayı küçükse) ve her değer kabaca eşit olarak temsil edilir mi? Bize çıktısını gösterSELECT TOP (20) [Status], c = COUNT(*) FROM dbo.Employee GROUP BY [Status] ORDER BY c DESC;
Aaron Bertrand

Yanıtlar:


11

Taramanın boş bir dize için bir aramadan kaynaklandığını sanmıyorum (ve bu durumda filtrelenmiş bir dizin ekleyebilirken, yalnızca sorgunun çok belirli varyasyonlarına yardımcı olacaktır). Bu sorguyu sağlayacağınız çeşitli parametre kombinasyonları (ve parametre değerleri) için optimize edilmemiş bir parametre kokusu ve tek bir plan kurbanı olma olasılığınız daha yüksektir.

Mutfak lavabosu da dahil olmak üzere her şeyi sağlamak için bir sorgu beklediğiniz için buna "mutfak lavabo" prosedürü diyorum.

Ben bu benim çözüm hakkında video var burada ve burada hem de bu konuda bir blog yazısı , ama aslında, böyle sorguları için elimizdeki en iyi deneyimi şudur:

  • İfadeyi dinamik olarak oluşturun - bu, hiçbir parametrenin sağlanmadığı sütunlardan bahseden cümleleri dışarıda bırakmanıza izin verir ve değerlerle iletilen gerçek parametreler için tam olarak optimize edilmiş bir planınızın olmasını sağlar .
  • KullanOPTION (RECOMPILE) - bu, belirli parametre değerlerinin yanlış plan planını zorlamasını önler, özellikle de veri eğriliği, kötü istatistikleriniz olduğunda veya bir ifadenin ilk yürütülmesi daha sonradan ve daha sık farklı bir plana yol açacak atipik bir değer kullandığında yararlı olur infaz.
  • Sunucu seçeneğini kullanınoptimize for ad hoc workloads - bu yalnızca bir kez kullanılan sorgu varyasyonlarının plan önbelleğinizi kirletmesini önler.

Geçici iş yükleri için optimizasyonu etkinleştir:

EXEC sys.sp_configure 'show advanced options', 1;
GO
RECONFIGURE WITH OVERRIDE;
GO
EXEC sys.sp_configure 'optimize for ad hoc workloads', 1;
GO
RECONFIGURE WITH OVERRIDE;
GO
EXEC sys.sp_configure 'show advanced options', 0;
GO
RECONFIGURE WITH OVERRIDE;

Prosedürünüzü değiştirin:

ALTER PROCEDURE dbo.Whatever
  @Status INT = NULL,
  @IsUserGotAnActiveDirectoryUser BIT = NULL
AS
BEGIN 
  SET NOCOUNT ON;
  DECLARE @sql NVARCHAR(MAX) = N'SELECT [IdNumber], [Code], [Status], 
     [Sex], [FirstName], [LastName], [Profession],
     [BirthDate], [HireDate], [ActiveDirectoryUser]
   FROM dbo.Employee -- please, ALWAYS schema prefix
   WHERE 1 = 1';

   IF @Status IS NOT NULL
     SET @sql += N' AND ([Status]=@Status)'

   IF @IsUserGotAnActiveDirectoryUser = 1
     SET @sql += N' AND ActiveDirectoryUser <> ''''';
   IF @IsUserGotAnActiveDirectoryUser = 0
     SET @sql += N' AND ActiveDirectoryUser = ''''';

   SET @sql += N' OPTION (RECOMPILE);';

   EXEC sys.sp_executesql @sql, N'@Status INT, @Status;
END
GO

İzleyebileceğiniz bu sorgu kümesine dayalı bir iş yükünüz olduğunda, yürütmeleri analiz edebilir ve hangilerinin ek veya farklı dizinlerden en çok yararlanacağını görebilirsiniz - bunu çeşitli açılardan, basit "hangi kombinasyonundan parametreleri en sık mı verilir? " "hangi bireysel sorgular en uzun çalışma süresine sahiptir?" Bu soruları yalnızca kodunuza göre yanıtlayamayız, yalnızca herhangi bir dizinin yalnızca desteklemeye çalıştığınız tüm olası parametre kombinasyonlarının bir alt kümesi için yardımcı olacağını önerebiliriz . Örneğin,@StatusNULL ise, bu kümelenmemiş dizine karşı arama yapılamaz. Dolayısıyla, kullanıcıların durumu umursamadığı durumlarda, diğer maddelere hitap eden bir dizininiz yoksa (ancak geçerli sorgu mantığınız dikkate alındığında böyle bir dizin de yararlı olmaz) - boş dize veya boş dize tam olarak seçici değildir).

Bu durumda, olası Statusdeğerler kümesine ve bu değerlerin ne kadar dağıtıldığına bağlı olarak, OPTION (RECOMPILE)gerekli olmayabilir. Ancak 100 satır verecek bazı değerleriniz ve yüz binlerce verim sağlayacak bazı değerleriniz varsa, bunu isteyebilirsiniz (bu sorgunun karmaşıklığı göz önüne alındığında marjinal olması gereken CPU maliyetinde bile), böylece olabildiğince çok durumda aramak. Değer aralığı yeterince sonluysa, dinamik SQL ile ilgili zor bir şey bile yapabilirsiniz, burada "Ben bu çok seçici bir değere sahibim @Status, yani belirli bir değer geçtiğinde, sorgu metninde bu küçük değişikliği yapın. bu farklı bir sorgu olarak kabul edilir ve bu parametre değeri için optimize edilmiştir. "


3
Bu yaklaşımı birçok kez kullandım ve optimize edicinin her şeyi yapması gerektiğini düşündüğünüz şekilde yapmasını sağlamak için harika bir yoldur. Kim Tripp burada benzer bir çözümden bahsediyor: sqlskills.com/blogs/kimberly/high-performance-procedures Ve birkaç yıl önce PASS'ta yaptığı ve neden çalıştığına dair gerçekten çılgın detaylara giren bir oturum videosu var. Diyor ki, Bay Bertrand'ın burada söylediklerine gerçekten bir ton katmıyor. Bu, herkesin alet kemerinde tutması gereken araçlardan biridir. Gerçekten tüm bu sorgular için bazı büyük ağrıları kurtarabilir.
ms

3

Feragatname : Bu cevaptaki bazı şeyler bir DBA çakması yapabilir. Saf performans açısından yaklaşıyorum - her zaman Index Scans aldığınızda Index Seeks'i nasıl edinirim.

Yoldan çekilince, işte gidiyor.

Sorgunuz, "çeşitli mutfak arama sorgusu" olarak bilinir. Bu, bir dizi olası arama koşulunu karşılamayı amaçlayan tek bir sorgudur. Kullanıcı @statusbir değere ayarlanırsa , bu duruma filtre uygulamak istersiniz. Öyleyse @status, NULLtüm durumları döndürün, vb.

Bu, indeksleme ile ilgili sorunlar getirir, ancak tüm arama koşullarınız "eşittir" ölçütleri olduğu için bu durumla ilgili değildir.

Bu anlaşılabilir:

WHERE [status]=@status

SQL Server'ın dizinde tek bir değer aramak yerine her satır için değerlendirilmesi gerektiğinden bu durum anlaşılmaz değilISNULL([status], 0) :

WHERE ISNULL([status], 0)=@status

Mutfak lavabo problemini daha basit bir biçimde yeniden yarattım:

CREATE TABLE #work (
    A    int NOT NULL,
    B    int NOT NULL
);

CREATE UNIQUE INDEX #work_ix1 ON #work (A, B);

INSERT INTO #work (A, B)
VALUES (1,  1), (2,  1),
       (3,  1), (4,  1),
       (5,  2), (6,  2),
       (7,  2), (8,  3),
       (9,  3), (10, 3);

Aşağıdakileri denerseniz, A, dizinin ilk sütunu olmasına rağmen bir Dizin Taraması alırsınız:

DECLARE @a int=4, @b int=NULL;

SELECT *
FROM #work
WHERE (@a IS NULL OR @a=A) AND
      (@b IS NULL OR @b=B);

Ancak bu bir Dizin Araması üretir:

DECLARE @a int=4, @b int=NULL;

SELECT *
FROM #work
WHERE @a=A AND
      @b IS NULL;

Yönetilebilir miktarda parametre kullandığınız sürece (sizin durumunuzda iki tane), muhtemelen sadece UNIONbir grup arama sorgusu yapabilirsiniz - temel olarak arama kriterlerinin tüm permütasyonları. Üç kriteriniz varsa, bu dağınık görünecek, dördü ile tamamen yönetilemeyecek. Uyarılmıştın.

DECLARE @a int=4, @b int=NULL;

SELECT *
FROM #work
WHERE @a=A AND
      @b IS NULL
UNION ALL
SELECT *
FROM #work
WHERE @a=A AND
      @b=B
UNION ALL
SELECT *
FROM #work
WHERE @a IS NULL AND
      @b=B
UNION ALL
SELECT *
FROM #work
WHERE @a IS NULL AND
      @b IS NULL;

Bu dördünden üçüncüsünde Dizin Araması kullanmak için ikinci bir dizine ihtiyacınız olacak (B, A). Sorgunuz bu değişikliklerle nasıl görünebilir (sorguyu daha okunabilir hale getirmek için yeniden düzenlemem dahil).

DECLARE @Status int = NULL,
        @IsUserGotAnActiveDirectoryUser bit = NULL;

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE [Status]=@Status AND
      @IsUserGotAnActiveDirectoryUser IS NULL

UNION ALL

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE [Status]=@Status AND
      @IsUserGotAnActiveDirectoryUser=1 AND ActiveDirectoryUser<>''

UNION ALL

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE [Status]=@Status AND
      @IsUserGotAnActiveDirectoryUser=0 AND (ActiveDirectoryUser IS NULL OR ActiveDirectoryUser='')

UNION ALL

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE @Status IS NULL AND
      @IsUserGotAnActiveDirectoryUser IS NULL

UNION ALL

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE @Status IS NULL AND
      @IsUserGotAnActiveDirectoryUser=1 AND ActiveDirectoryUser<>''

UNION ALL

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE @Status IS NULL AND
      @IsUserGotAnActiveDirectoryUser=0 AND (ActiveDirectoryUser IS NULL OR ActiveDirectoryUser='');

... artı Employeeiki dizin sütunu ters çevrilmiş olarak ek bir dizine ihtiyacınız olacaktır .

Bütünlük için, bunun x=@xdolaylı olarak xolamayacağı anlamına gelir NULLçünkü NULLasla eşit değildir NULL. Bu, sorguyu biraz basitleştirir.

Ve evet, Aaron Bertrand'ın dinamik SQL cevabı çoğu durumda daha iyi bir seçimdir (yani, yeniden derlemelerle yaşayabildiğinizde).


3

Temel sorunuz "Neden" gibi görünüyor ve birkaç yıl önce Adam Machanic'in TechEd'deki bu Büyük sunumunun 55. dakikasını bulabileceğinizi düşünüyorum .

Dakika 55 5 dakika söz ama tüm sunum zaman ayırmaya değer. Sorgunuz için sorgu planına bakarsanız, arama için artık tahminler bulduğuna eminim. Temel olarak SQL, dizinin tüm bölümlerini "göremez", çünkü bazıları eşitsizlikler ve diğer koşullar tarafından gizlenmiştir. Sonuç, Predicate'e dayalı bir süper set için bir indeks taramasıdır. Bu sonuç biriktirilir ve daha sonra artık yüklem kullanılarak yeniden taranır.

Tarama Operatörünün (F4) özelliklerini kontrol edin ve özellik listesinde hem "Arama İsteği" hem de "Tahmin Et" e sahip olup olmadığınıza bakın.

Diğerlerinin belirttiği gibi, sorguyu olduğu gibi dizine eklemek zordur. Son zamanlarda birçok benzer ürün üzerinde çalışıyorum ve her biri farklı bir çözüm gerektiriyordu. :(


0

Dizin taraması için dizin aramasının tercih edilip edilmediğini sorgulamadan önce, temel kural, temel alınan tablonun toplam satırlarına göre kaç satır döndürüldüğünü kontrol etmektir. Örneğin, sorgunuzun 1 milyon satırdan 10 satır döndürmesini bekliyorsanız, dizin araması muhtemelen dizin taramasından çok tercih edilir. Ancak, sorgudan birkaç bin satır (veya daha fazla) döndürülecekse, dizin araması mutlaka SEÇİLMEYEBİLİR.

Sorgunuz karmaşık değil, bu nedenle bir yürütme planı gönderebilirseniz, size yardımcı olacak daha iyi fikirlerimiz olabilir.


1 milyonluk bir tablodan birkaç bin satırı filtrelemek, hala bir arama yapmak istiyorum - tüm tabloyu taramaya kıyasla hala büyük bir performans iyileştirmesi.
Daniel Hutmacher

-6

bu sadece biçimlendirilmiş orijinal

DECLARE @Status INT = NULL,
        @IsUserGotAnActiveDirectoryUser BIT = NULL    

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName], [Profession],
       [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE (@Status IS NULL OR [Status]=@Status)  
AND (            @IsUserGotAnActiveDirectoryUser IS NULL 
      OR (       @IsUserGotAnActiveDirectoryUser IS NOT NULL 
           AND (     @IsUserGotAnActiveDirectoryUser = 1 
                 AND ActiveDirectoryUser <> '') 
           OR  (     @IsUserGotAnActiveDirectoryUser = 0 
                 AND ActiveDirectoryUser =  '')
         )
    )

bu revizyon - bu% 100 emin değilim ama (belki) denemek bir tane
bile VEYA muhtemelen
ActiveDirectoryUser null kırmak bir sorun olacak

  WHERE isnull(@Status, [Status]) = [Status]
    AND (      (     isnull(@IsUserGotAnActiveDirectoryUser, 1) = 1 
                 AND ActiveDirectoryUser <> '' ) 
           OR  (     isnull(@IsUserGotAnActiveDirectoryUser, 0) = 0 
                 AND ActiveDirectoryUser =  '' )
        )

3
Bu cevabın OP'nin sorusunu nasıl çözdüğü açık değil.
Erik

@Erik Belki OP'nin denemesine izin verebilir miyiz? İki VEYA gitti. Bunun sorgu performansına yardımcı olamayacağından emin misiniz?
paparazzo

@ ypercubeᵀᴹ IsUserGotAnActiveDirectoryUser IS NOT NULL kaldırıldı. Bu iki gereksiz bir OR kaldırmak ve IsUserGotAnActiveDirectoryUser IS NULL kaldırmak. Bu sorgunun OP'den sonra hızlı çalışmayacağından emin misiniz?
paparazzo

@ ypercubeᵀᴹ Bir çok şey yapabilirdi. Ben daha basit aramıyorum. İki Veya gitti. Veya genellikle sorgu planları için kötüdür. Burada bir tür kulüp var ve kulübün bir parçası değilim. Ama bunu yaşamak için yapıyorum ve bildiklerimin işe yaradığını yazıyorum. Cevaplarım aşağı oylardan etkilenmiyor.
paparazzo
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.