Bir sorgu planı oluşturmanın maliyeti nasıl ölçülür veya bulunur?


18

Parametre koklama bir "kötü" yürütme planı plan önbelleğine inmeye neden olur ve saklı yordamın sonraki yürütme çok yavaş neden tipik bir durum var. Bu sorunu yerel değişkenlerle "çözebilirim" OPTIMIZE FOR ... UNKNOWN, ve OPTION(RECOMPILE). Ancak, ben de sorgu içine dalış ve optimize etmeye çalışabilirsiniz.

Ben olmadığını belirlemek için çalışıyorum gerektiğini saptamak problem ile sınırlı zaman verilen Ben maliyetini öğrenmek istiyorum: değil yapıyor. Gördüğüm gibi, sadece sopa ile OPTION(RECOMPILE)net etkisi, sorgu her çalıştırıldığında bir sorgu planı yeniden oluşturulmasıdır . Yani, bence bilmem gerek:

Bir sorgu planı oluşturmanın maliyeti nedir?

Kendi sorumu yanıtlamak için Google'ı aradım (örneğin bu sorgu ile ) ve dm_exec_query_statsDMV sütunlarının belgelerini inceledim . Ayrıca bu bilgiyi bulmak için "Gerçek Sorgu Planı" için SSMS çıkış penceresini inceledim. Sonunda DBA.SE'yi aradım . Bunların hiçbiri cevap vermedi.

Biri bana söyleyebilir mi? Plan oluşturmak için gereken süreyi bulmak veya ölçmek mümkün müdür?


5
Benjamin Nevarez'in SQL Server Sorgu Optimize Edici'nin bir kopyasını almanızı tavsiye ederim . Bedava. Bölüm 5 'Optimizasyon Süreci', sorgunuzun derleme süresini belirlemenize yardımcı olabilir. En azından, bir sorgu planı oluşturmak için optimize edicinin neler geçirdiği hakkında bilgilendirici.
Mark Sinkinson

Yanıtlar:


18

Bir sorgu planı oluşturmanın maliyeti nedir?

Sorgu planında kök düğümün özelliklerine bakabilirsiniz, örneğin:

Kök özellikleri özü
(ücretsiz Sentry One Plan Explorer'dan ekran görüntüsü )

Bu bilgilere ayrıca plan önbelleğini sorgulayarak da ulaşabilirsiniz, örneğin aşağıdaki ilişkilere dayalı bir sorgu kullanarak:

WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT 
    CompileTime = c.value('(QueryPlan/@CompileTime)[1]', 'int'),
    CompileCPU = c.value('(QueryPlan/@CompileCPU)[1]', 'int'),
    CompileMemory = c.value('(QueryPlan/@CompileMemory)[1]', 'int'),
    ST.[text],
    QP.query_plan
FROM sys.dm_exec_cached_plans AS CP
CROSS APPLY sys.dm_exec_query_plan(CP.plan_handle) AS QP
CROSS APPLY sys.dm_exec_sql_text(CP.plan_handle) AS ST
CROSS APPLY QP.query_plan.nodes('ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS N(c);

Sonuç parçası

Bu tür sorguları ele almak için sahip olduğunuz seçeneklerin tam bir tedavisi için Erland Sommarskog'un yakın zamanda güncellenen makalesine bakın .


4

"Maliyet" varsayımı zaman açısındandır (ancak ;-) açısından başka ne olabileceğinden emin olmasa da, en azından aşağıdaki gibi bir şey yaparak bunu anlayabilmelisiniz:

DBCC FREEPROCCACHE WITH NO_INFOMSGS;

SET STATISTICS TIME ON;

EXEC sp_help 'sys.databases'; -- replace with your proc

SET STATISTICS TIME OFF;

"Mesajlar" sekmesinde bildirilen ilk öğe şu şekilde olmalıdır:

SQL Server ayrıştırma ve derleme zamanı:

Ben bunu en az 10 kez çalıştırmak ve hem "CPU" hem de "Geçen" milisaniye ortalama.

İdeal olarak bunu Üretim'de çalıştırırsınız, böylece gerçek zamanlı bir tahmin elde edebilirsiniz, ancak nadiren insanların Üretim'deki plan önbelleğini temizlemelerine izin verilir. Neyse ki, SQL Server 2008'den başlayarak, belirli bir planı önbellekten temizlemek mümkün oldu. Bu durumda aşağıdakileri yapabilirsiniz:

DECLARE @SQL NVARCHAR(MAX) = '';
;WITH cte AS
(
  SELECT DISTINCT stat.plan_handle
  FROM sys.dm_exec_query_stats stat
  CROSS APPLY sys.dm_exec_text_query_plan(stat.plan_handle, 0, -1) qplan
  WHERE qplan.query_plan LIKE N'%sp[_]help%' -- replace "sp[_]help" with proc name
)
SELECT @SQL += N'DBCC FREEPROCCACHE ('
               + CONVERT(NVARCHAR(130), cte.plan_handle, 1)
               + N');'
               + NCHAR(13) + NCHAR(10)
FROM cte;
PRINT @SQL;
EXEC (@SQL);

SET STATISTICS TIME ON;

EXEC sp_help 'sys.databases' -- replace with your proc

SET STATISTICS TIME OFF;

Bununla birlikte, "kötü" önbelleğe alınmış plana neden olan parametreler için geçirilen değerlerin değişkenliğine bağlı olarak, aralarında bir orta nokta olduğunu düşünmek için başka bir yöntem vardır. OPTION(RECOMPILE) ve OPTION(OPTIMIZE FOR UNKNOWN)Dinamik SQL. Evet, söyledim. Ve hatta parametreleştirilmemiş Dinamik SQL demek. İşte nedeni.

En azından bir veya daha fazla giriş parametresi değeri açısından eşit olmayan bir dağılımı olan verileriniz açıkça var. Bahsedilen seçeneklerin dezavantajları şunlardır:

  • OPTION(RECOMPILE)her yürütme için bir plan oluşturur ve tekrar iletilen parametre değerleri önceki çalıştırma (lar) ile aynı olsa bile hiçbir zaman planın yeniden kullanımından asla yararlanamazsınız . Sıklıkla çağrılan proc'lar için - birkaç saniyede bir veya daha sık - bu sizi ara sıra korkunç durumlardan koruyacaktır, ancak yine de sizi her zaman o kadar da harika olmayan bir durumda bırakacaktır.

  • OPTION(OPTIMIZE FOR (@Param = value)) bu özel değere dayalı bir plan oluşturacak ve bu da birkaç duruma yardımcı olabilir, ancak sizi şu anki konuya açık bırakacaktır.

  • OPTION(OPTIMIZE FOR UNKNOWN)ortalama bir dağılımın ne kadar olduğuna bağlı olarak bazı sorgulara yardımcı olacak, ancak diğerlerine zarar verecek bir plan oluşturacaktır. Bu, yerel değişkenleri kullanma seçeneğiyle aynı olmalıdır.

Bununla birlikte, dinamik SQL, doğru bir şekilde yapıldığında , geçirilen çeşitli değerlerin ideal olan kendi ayrı sorgu planlarına sahip olmalarını sağlayacaktır (olacakları kadar). Buradaki ana maliyet, geçirilen değerlerin çeşitliliği arttıkça, önbellekteki yürütme planlarının sayısının artması ve belleğin yer almasıdır. Düşük maliyetler:

  • SQL Enjeksiyonlarını önlemek için dize parametrelerini doğrulama ihtiyacı

  • Dinamik SQL doğrudan tablo izinleri gerektirdiğinden ideal güvenlik soyutlamasını korumak için bir Sertifika ve Sertifika Tabanlı Kullanıcı kurması gerekebilir.

Yani, saniyede birden fazla çağrılan ve her biri milyonlarca satır içeren birden çok tabloya çarptığım proclarım olduğunda bu durumu nasıl yönettim. Denedim OPTION(RECOMPILE)ama parametre koklama / kötü önbelleğe alınmış plan sorunu olmayan olguların% 99'unda bu sürece çok zarar verdi. Ve lütfen bu süreçlerden birinin içinde yaklaşık 15 sorgu bulunduğunu ve yalnızca 3 - 5'inin burada açıklandığı gibi Dinamik SQL'e dönüştürüldüğünü unutmayın; Belirli bir sorgu için gerekli olmadıkça dinamik SQL kullanılmadı.

  1. Saklı yordam için birden çok giriş parametresi varsa, hangilerinin çok farklı veri dağılımlarına sahip (ve dolayısıyla bu soruna neden olan) sütunlarla kullanıldığını ve hangilerinin daha eşit dağılımlı (ve olmamalıdır) sütunlarla kullanıldığını öğrenin bu soruna neden olan).

  2. Eşit olarak dağıtılmış sütunlarla ilişkilendirilmiş proc giriş parametreleri için parametreleri kullanarak Dinamik SQL dizesini oluşturun. Bu parametrelendirme, bu sorgu ile ilgili önbellekteki yürütme planlarında ortaya çıkan artışın azaltılmasına yardımcı olur.

  3. Çok çeşitli dağılımlarla ilişkili kalan parametreler için, bunlar dinamik SQL olarak değişmez değerler olarak birleştirilmelidir. Benzersiz bir sorgu, sorgu metninde yapılan herhangi bir değişiklikle belirlendiğinden, sahip WHERE StatusID = 1olmak , sahip olmaktan farklı bir sorgu ve dolayısıyla farklı sorgu planıdır WHERE StatusID = 2.

  4. Sorgunun metnine birleştirilecek proc giriş parametrelerinden herhangi biri dizeler ise, SQL Enjeksiyonuna karşı koruma sağlamak için doğrulanmaları gerekir (ancak, geçirilen dizeler, bir kullanıcı değil, yine de). En azından REPLACE(@Param, '''', '''''')tek tırnakların tek tırnaklardan kaçmasını sağlamak için yapın.

  5. Gerekirse, bir Kullanıcı oluşturmak için kullanılacak bir Sertifika oluşturun ve saklı yordamı, [public]aksi takdirde bu tür izinlere sahip olmaması gereken Kullanıcılara değil, yalnızca yeni Sertifika Tabanlı Kullanıcıya verilecek şekilde saklayın. .

Örnek proc:

CREATE PROCEDURE MySchema.MyProc
(
  @Param1 INT,
  @Param2 DATETIME,
  @Param3 NVARCHAR(50)
)
AS
SET NOCOUNT ON;

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'
     SELECT  tab.Field1, tab.Field2, ...
     FROM    MySchema.SomeTable tab
     WHERE   tab.Field3 = @P1
     AND     tab.Field8 >= CONVERT(DATETIME, ''' +
  CONVERT(NVARCHAR(50), @Param2, 121) +
  N''')
     AND     tab.Field2 LIKE N''' +
  REPLACE(@Param3, N'''', N'''''') +
  N'%'';';

EXEC sp_executesql
     @SQL,
     N'@P1 INT',
     @P1 = @Param1;

Cevaplamak için biraz zaman ayırdığınız için teşekkürler! @ PaulWhite yaklaşımını kullanarak elde ettiğim sonuçtan daha düşük bir faktör 3 göz önüne alındığında, derleme zamanı nasıl elde edileceği hakkında ilk biraz hakkında biraz şüpheci değilim . - Dinamik SQL bitindeki ikincisi ilginçtir (uygulanması da zaman gerektirir; en azından benim sorgumu tokatlamaktan daha fazlası ) ve bu sproc entegrasyon testlerinde iyi kullanıldığından bana çok fazla zarar vermez . - Her durumda: anlayışınız için teşekkürler! OPTION
Jeroen
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.