Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

[PL] Parametry procedur składowanych a plan wykonania

Ostatnimi czasy dość często powraca pewien problem z wydajnością procedur składowanych. Najczęściej ludzie definiują ów problem następująco:

Mam procedurę składowaną, która przyjmuje parametr (-y) wykorzystywany we wnętrzu procedury w klauzuli WHERE polecenia SELECT. Jeżeli podaję wartość parametru (-ów) przy wywołaniu procedury, to procedura wykonuje się znacznie dłużej niż w sytuacji, gdy podam wartość parametru wprost.

Czemu tak się dzieje? Gdy definiujemy parametry procedury, które wewnątrz procedury służą do filtrowania rekordów, SQL Server nie wie, jakie wartości tych parametrów będą podawane przez użytkowników. Jeżeli nie będzie w cache'u procedur (cache'u przechowującym skompilowane plany zapytań) planu danej procedury lub też procedura zostanie zrekompilowana (a może zostać zrekompilowana z różnych powodów) i użytkownik wywoła procedurę z wartościami parametrów nie gwarantującymi odpowiedniej selektywności zapytania, SQL Server może zapamiętać plan wykonania procedury, który nie będzie optymalny dla większości jej wywołań. Zdarza się też, że z uwagi na trudne do przewidzenia wartości parametrów przy bardziej złożonych zapytaniach wewnątrz procedur optymalizator zapytań wybiera nieoptymalny plan wykonania. Przykład (z książki "Inside Microsoft SQL Server 2005: T-SQL Programming" - Itzik Ben-Gan, Dejan Sarka i Roger Wolter, MS Press 2007):

USE Northwind
GO

IF OBJECT_ID('dbo.usp_GetOrders') IS NOT NULL
  DROP PROC dbo.usp_GetOrders
GO

CREATE PROC dbo.usp_GetOrders
  @odate AS DATETIME
AS
SET NOCOUNT ON
SELECT OrderID, CustomerID, EmployeeID, OrderDate
FROM dbo.Orders
WHERE OrderDate >= @odate
GO

Powyższa procedura jest bardzo prosta, ale świetnie nadaje się do zilustrowania problemu. Załóżmy, że pierwsze wykonanie procedury będzie następujące:

EXEC dbo.usp_GetOrders '19960101'

Plan wykonania powyższego wywołania procedury pokazuje skanowanie indeksu clustered.

Plan ten nie jest zły dla tego konkretnego wywołania, ale załóżmy, że nie jest optymalny dla większości wywołań, jakie będą wykonywane na tabeli dbo.Orders. Drugie wywołanie procedury (z inną wartością parametru) zostanie wykonane z tym samym planem wykonania.

EXEC dbo.usp_GetOrders '19980506'

Jeżeli wykonamy teraz zapytanie z wnętrza procedury podstawiając podaną wyżej wartość parametru wprost, to plan wykonania będzie inny (lepszy, bo wykorzystujący indeks na kolumnie OrderDate).

SELECT OrderID, CustomerID, EmployeeID, OrderDate
FROM dbo.Orders
WHERE OrderDate >= '19980506'

Byłoby idealnie, gdyby taki plan wykonania nie był nigdy zastępowany pokazanym wcześniej planem zawierającym skanowanie indeksu clustered. Oczywiście, można uruchomić procedurę po raz drugi z opcją WITH RECOMPILE, co spowoduje, że plan będzie optymalny (Index Seek).

EXEC dbo.usp_GetOrders '19980506' WITH RECOMPILE

Jednak ta metoda nie gwarantuje, że za jakiś czas plan nieoptymalny (Clustered Index Scan) nie zastąpi w cache'u planu optymalnego (Index Seek).

Istnieje co najmniej kilka metod pozwalających na zachowanie planu wykonania w cache'u nawet w przypadku rekompilacji procedury z wartościami parametrów implikującymi niską selektywność.

Najstarszą z metod jest oszukanie optymalizatora przez podanie dodatkowych parametrów z widocznymi wartościami domyślnymi:

ALTER PROC dbo.usp_GetOrders
  @odate DATETIME,
  @tempdate DATETIME = '19980506'
AS
SET NOCOUNT ON
SET @tempdate = @odate
SELECT OrderID, CustomerID, EmployeeID, OrderDate
FROM dbo.Orders
WHERE OrderDate >= @tempdate;
GO

Oczywiście, wygląda to nieco dziwacznie, ale jest to skuteczna metoda na zachowanie planu. Wystarczy wykonać powyższą składnię ALTER PROC, a następnie wywołać procedurę z odpowiednią wartością parametru @odate (z wartością gwarantującą selektywność). Po wykonaniu tych czynności nawet wywołanie procedury z rekompilacją nie spowoduje zmiany planu w cache'u.

SQL Server 2005 oferuje pewną opcję, która także potrafi zagwarantować plan wykonania. Tą opcją jest OPTIMIZE FOR. Przykład użycia tej opcji w przypadku omawianej procedury usp_GetOrders:

ALTER PROC dbo.usp_GetOrders
  @odate DATETIME
AS
SET NOCOUNT ON
SELECT OrderID, CustomerID, EmployeeID, OrderDate
FROM dbo.Orders
WHERE OrderDate >= @odate
OPTION(OPTIMIZE FOR(@odate = '19980506'));
GO

I wreszcie rozwiązanie trzecie - odpowiednie zwłaszcza, gdy w procedurze znajduje się wiele zapytań, a tylko niektóre muszą zmieniać swój plan wykonania w zależności od wartości parametrów procedury. Można częściowo rekompilować procedury powodując, że pewne polecenia wewnątrz procedury będą miały plan wykonania zawsze adekwatny do wywołania procedury.

ALTER PROC dbo.usp_GetOrders
  @odate DATETIME
AS
SET NOCOUNT ON
SELECT OrderID, CustomerID, EmployeeID, OrderDate
FROM dbo.Orders
WHERE OrderDate >= @odate
OPTION(RECOMPILE);
GO

Tak zdefiniowana procedura gwarantuje, że w przypadku zapytania selektywnego będzie wykonywane skanowanie indeksu, a w przypadku niskiej selektywności optymalizator wybierze skanowanie indeksu clustered.

Temat optymalizacji procedur oraz ich rekompilacji jest oczywiście dużo szerszy, ale powyższy trywialny przykład pokazuje, że używanie procedur nie jest bezproblemowe i czasem trzeba pomóc optymalizatorowi, jeżeli produkowane przez niego plany wykonania nie spełniają naszych oczekiwań.

Ze swojej strony polecam whitepaper Batch Compilation, Recompilation, and Plan Caching Issues in SQL Server 2005, w którym opisane są mechanizmy funkcjonowania cache'u procedur, przyczyny automatycznej rekompilacji oraz metody rekompilacji dostępne dla użytkownika. 

Opublikowane 9 listopada 2007 10:14 przez brejk

Powiadamianie o komentarzach

Jeżeli chciałbyś otrzymywać email gdy ta wypowiedź zostanie zaktualizowana, to zarejestruj się tutaj

Subskrybuj komentarze za pomocą RSS

Komentarze:

# re: Parametry procedur składowanych a plan wykonania

9 listopada 2007 13:11 by arkadiusz.wasniewski

Mniam... 10/10

Arek

# re: Parametry procedur składowanych a plan wykonania

11 listopada 2007 22:55 by Daniel Arak

Pawel super! Przekazuje link dalej - 10/10 :)

# re: Parametry procedur składowanych a plan wykonania

15 listopada 2007 17:25 by A

Dzisiaj akurat miałem dokładnie taki problem jak opisujesz wyżej, z tą różnicą że był to SQL 2000.  

zapytanie miało postać:

Select * form T1

left join t2....

left join t3....

where t3.ID = @id

Sztuczka z dodatkową zmienną nie pomogła, inne rozwiązania przedstawiane przez Ciebie nie mogły być zastosowane w SQL 2000. Pomogło coś zupełnie innego.

Zamieniłem kolejność tych left join

czyli:

select * from T1

left join t3 ...

left join t2 ...

where t3.id = @id

Ciekawe zachowanie SQL.

Pozdrawiam Jarek Jeziak

# re: Parametry procedur składowanych a plan wykonania

15 listopada 2007 23:46 by brejk

Jarek,

W SQL Server 2000 znajdziesz takich akcji więcej. Może dałbyś radę opracować prosty przykład z tymi left joinami? Bardzo chętnie go tu umieszczę, oczywiście za Twoim pozwoleniem.

Pozdrawiam

Paweł

# re: Parametry procedur składowanych a plan wykonania

19 listopada 2007 09:48 by Jarek Jeziak

Nie ma problemu, postaram się opisać problem i podeśle.

Nie mogę "żywcem" zrobić skryptów z tabelek. Chciałbym odtworzyć warunki w jakich wychodził ten problem.

# re: Parametry procedur składowanych a plan wykonania

26 listopada 2007 10:50 by RockY

Dzięki - bardzo użyteczny kawałek tekstu

# Metoda AddWithValue i cache planów wykonania

28 listopada 2007 21:04 by SQLGEEK

Kontynuując niejako temat cache'u planów wykonania zapytań w SQL Server 2005 (vide Parametry procedur

# re: Parametry procedur składowanych a plan wykonania

10 marca 2008 10:00 by arkadiusz.wasniewski

A jak wygląda Pawle sytuacja dla funkcji, która przyjmuje parametry, wywołuje zapytanie z tymi parametrami (mogą być NULL) i zwraca obliczoną wartość? W funkcjach nie mogą użyć opcji RECOMPILE dla przykładu. Hę?

Arek

Co o tym myślisz?

(wymagane) 
wymagane 
(wymagane) 

  
Wprowadź kod: (wymagane)

About brejk

MCT, SQL Server geek