Database
 sql >> Baza danych >  >> RDS >> Database

Przypadek użycia dla sp_prepare / sp_prepexec

Są funkcje, których wielu z nas unika, takie jak kursory, wyzwalacze i dynamiczny SQL. Nie ma wątpliwości, że każdy z nich ma swoje przypadki użycia, ale kiedy widzimy wyzwalacz z kursorem w dynamicznym SQL, może nas to wywołać (potrójny whammy).

Przewodniki po planach i sp_prepare są na podobnej łódce:gdybyś zobaczył, że używam jednego z nich, uniósłbyś brew; gdybyś zobaczył, że używam ich razem, prawdopodobnie sprawdziłbyś moją temperaturę. Ale, podobnie jak w przypadku kursorów, wyzwalaczy i dynamicznego SQL, mają swoje przypadki użycia. Niedawno natknąłem się na scenariusz, w którym używanie ich razem było korzystne.

Tło

Mamy dużo danych. I wiele aplikacji działających na tych danych. Niektóre z tych aplikacji są trudne lub niemożliwe do zmiany, w szczególności aplikacje gotowe od osób trzecich. Tak więc, gdy ich skompilowana aplikacja wysyła zapytania ad hoc do SQL Server, szczególnie w postaci przygotowanej instrukcji, i gdy nie mamy swobody dodawania lub zmiany indeksów, kilka możliwości dostrajania jest natychmiast niedostępnych.

W tym przypadku mieliśmy tabelę z kilkoma milionami wierszy. Uproszczona i oczyszczona wersja:

CREATE TABLE dbo.TheThings
(
  ThingID    bigint NOT NULL,
  TypeID     uniqueidentifier NOT NULL,
  dt1        datetime NOT NULL DEFAULT sysutcdatetime(),
  dt2        datetime NOT NULL DEFAULT sysutcdatetime(),
  dt3        datetime NOT NULL DEFAULT sysutcdatetime(),
  CONSTRAINT PK_TheThings PRIMARY KEY (ThingID)
);
 
CREATE INDEX ix_type ON dbo.TheThings(TypeID);
 
SET NOCOUNT ON;
GO
 
DECLARE @guid1 uniqueidentifier = 'EE81197A-B2EA-41F4-882E-4A5979ACACE4',
        @guid2 uniqueidentifier = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F';
 
INSERT dbo.TheThings(ThingID, TypeID)
  SELECT TOP (1000) 1000 + ROW_NUMBER() OVER (ORDER BY name), @guid1
    FROM sys.all_columns;
 
INSERT dbo.TheThings(ThingID, TypeID)
  SELECT TOP (1) 2500, @guid2
    FROM sys.all_columns;
 
INSERT dbo.TheThings(ThingID, TypeID)
  SELECT TOP (1000) 3000 + ROW_NUMBER() OVER (ORDER BY name), @guid1
    FROM sys.all_columns;

Przygotowane oświadczenie z aplikacji wyglądało tak (jak widać w pamięci podręcznej planu):

(@P0 varchar(8000))SELECT * FROM dbo.TheThings WHERE TypeID = @P0

Problem polega na tym, że dla niektórych wartości TypeID , byłoby wiele tysięcy wierszy. W przypadku innych wartości będzie ich mniej niż 10. Jeśli na podstawie jednego typu parametru zostanie wybrany (i ponownie wykorzystany) niewłaściwy plan, może to stanowić problem dla innych. W przypadku zapytania, które pobiera kilka wierszy, chcemy, aby wyszukiwanie indeksu z wyszukiwaniami pobierało dodatkowe niepokryte kolumny, ale w przypadku zapytania zwracającego 700 tys. wierszy chcemy po prostu skanować indeks klastrowy. (Idealnie, indeks obejmowałby, ale tej opcji nie było tym razem na kartach).

W praktyce aplikacja zawsze otrzymywała wariację skanowania, mimo że była to ta, która była potrzebna w około 1% przypadków. 99% zapytań korzystało ze skanowania 2 milionów wierszy, podczas gdy mogły użyć wyszukiwania + 4 lub 5 wyszukiwań.

Możemy łatwo odtworzyć to w Management Studio, uruchamiając to zapytanie:

DBCC FREEPROCCACHE;
DECLARE @P0 uniqueidentifier = 'EE81197A-B2EA-41F4-882E-4A5979ACACE4';
SELECT * FROM dbo.TheThings WHERE TypeID = @P0;
GO
 
DBCC FREEPROCCACHE;
DECLARE @P0 uniqueidentifier = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F';
SELECT * FROM dbo.TheThings WHERE TypeID = @P0;
GO

Plany wróciły tak:

Oszacowanie w obu przypadkach wynosiło 1000 wierszy; ostrzeżenia po prawej są spowodowane pozostałościami we/wy.

Jak możemy upewnić się, że zapytanie dokonało właściwego wyboru w zależności od parametru? Musielibyśmy dokonać ponownej kompilacji bez dodawania wskazówek do zapytania, włączania flag śledzenia lub zmiany ustawień bazy danych.

Jeśli uruchomiłem zapytania niezależnie za pomocą OPTION (RECOMPILE) , w razie potrzeby otrzymam zapytanie:

DBCC FREEPROCCACHE;
 
DECLARE @guid1 uniqueidentifier = 'EE81197A-B2EA-41F4-882E-4A5979ACACE4',
        @guid2 uniqueidentifier = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F';
 
SELECT * FROM dbo.TheThings WHERE TypeID = @guid1 OPTION (RECOMPILE);
SELECT * FROM dbo.TheThings WHERE TypeID = @guid2 OPTION (RECOMPILE);

Dzięki RECOMPILE otrzymujemy dokładniejsze oszacowania i szukamy, kiedy ich potrzebujemy.

Ale znowu nie mogliśmy bezpośrednio dodać wskazówki do zapytania.

Wypróbujmy przewodnik po planie

Wiele osób ostrzega przed przewodnikami po planach, ale byliśmy tu trochę w kącie. Zdecydowanie wolelibyśmy zmienić zapytanie lub indeksy, gdybyśmy mogli. Ale to może być kolejna najlepsza rzecz.

EXEC sys.sp_create_plan_guide   
  @name   = N'TheThingGuide',
  @stmt   = N'SELECT * FROM dbo.TheThings WHERE TypeID = @P0',
  @type   = N'SQL',
  @params = N'@P0 varchar(8000)',
  @hints  = N'OPTION (RECOMPILE)';

Wydaje się proste; testowanie to jest problem. Jak symulujemy przygotowane zestawienie w Management Studio? Jak możemy mieć pewność, że aplikacja otrzymuje plan z przewodnikiem i że jest to wyraźnie spowodowane przewodnikiem po planie?

Jeśli spróbujemy zasymulować to zapytanie w SSMS, zostanie to potraktowane jako oświadczenie ad hoc, a nie przygotowane oświadczenie, i nie mogłem tego uzyskać, aby pobrać przewodnik po planie:

DECLARE @P0 varchar(8000) = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; -- also tried uniqueidentifier
SELECT * FROM dbo.TheThings WHERE TypeID = @P0

Dynamiczny SQL również nie działał (to również zostało potraktowane jako instrukcja ad hoc):

DECLARE @sql nvarchar(max) = N'SELECT * FROM dbo.TheThings WHERE TypeID = @P0', 
        @params nvarchar(max) = N'@P0 varchar(8000)', -- also tried uniqueidentifier
        @P0 varchar(8000) = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F';
 
EXEC sys.sp_executesql @sql, @params, @P0;

A nie mogłem tego zrobić, bo to też nie podniosłoby przewodnika po planie (tu przejmuje parametryzacja, a ja nie miałem swobody w zmianie ustawień bazy danych, nawet jeśli miałoby to być traktowane jak przygotowana wypowiedź) :

SELECT * FROM TheThings WHERE TypeID = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F';

Nie mogę sprawdzić pamięci podręcznej planu dla zapytań uruchomionych z aplikacji, ponieważ buforowany plan nie wskazuje nic na temat użycia przewodnika planu (SSMS wstrzykuje te informacje do pliku XML podczas generowania rzeczywistego planu). A jeśli zapytanie naprawdę zawiera wskazówkę dotyczącą RECOMPILE, którą przekazuję w przewodniku po planie, to w jaki sposób mógłbym kiedykolwiek zobaczyć jakiekolwiek dowody w pamięci podręcznej planu?

Spróbujmy sp_prepare

W swojej karierze używałem sp_prepare rzadziej niż przewodników po planach i nie zalecałbym używania go do kodu aplikacji. (Jak wskazuje Erik Darling, oszacowanie można wyciągnąć z wektora gęstości, a nie z wąchania parametru.)

W moim przypadku nie chcę go używać ze względu na wydajność, chcę go użyć (wraz z sp_execute) do symulacji przygotowanej instrukcji pochodzącej z aplikacji.

DECLARE @o int;
EXEC sys.sp_prepare @o OUTPUT, N'@P0 varchar(8000)',
     N'SELECT * FROM dbo.TheThings WHERE TypeID = @P0';
 
EXEC sys.sp_execute @o,  'EE81197A-B2EA-41F4-882E-4A5979ACACE4'; -- PK scan
EXEC sys.sp_execute @o,  'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; -- IX seek + lookup

SSMS pokazuje nam, że przewodnik po planie był używany w obu przypadkach.

Nie będziesz mógł sprawdzić pamięci podręcznej planu pod kątem tych wyników z powodu ponownej kompilacji. Ale w scenariuszu takim jak mój, powinieneś być w stanie zobaczyć efekty w monitorowaniu, jawnym sprawdzaniu za pomocą zdarzeń rozszerzonych lub obserwowaniu złagodzenia symptomu, który skłonił Cię do zbadania tego zapytania w pierwszej kolejności (należy pamiętać, że średni czas działania, zapytanie na statystyki itp. może mieć wpływ dodatkowa kompilacja).

Wniosek

Był to jeden przypadek, w którym przewodnik planu był korzystny, a sp_prepare był przydatny w sprawdzaniu, czy będzie działał dla aplikacji. Te nieczęsto się przydają i rzadziej razem, ale dla mnie było to ciekawe połączenie. Nawet bez przewodnika po planie, jeśli chcesz używać programu SSMS do symulowania aplikacji wysyłającej przygotowane zestawienia, sp_prepare jest twoim przyjacielem. (Zobacz także sp_prepexec, który może być skrótem, jeśli nie próbujesz zweryfikować dwóch różnych planów dla tego samego zapytania.)

Zauważ, że to ćwiczenie niekoniecznie miało na celu uzyskanie lepszych wyników przez cały czas – miało to na celu spłaszczenie wariancji wydajności. Ponowne kompilacje oczywiście nie są darmowe, ale zapłacę niewielką karę, aby 99% moich zapytań zostało wykonanych w ciągu 250 ms, a 1% w ciągu 5 sekund, zamiast utknąć z planem, który jest absolutnie okropny dla 99% zapytań lub 1% zapytań.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Grupowanie danych o dacie i godzinie

  2. Łączenie się z Sage z Javy

  3. Instrukcja SQL WHERE

  4. Korzyści i bezpieczeństwo w usłudze relacyjnej bazy danych Amazon

  5. SQL, jak usunąć dane i tabele