sql >> Databáze >  >> RDS >> Database

Případ použití pro sp_prepare / sp_prepexec

Existují funkce, kterým se mnozí z nás vyhýbají, jako jsou kurzory, spouštěče a dynamické SQL. Není pochyb o tom, že každý z nich má své případy použití, ale když vidíme spouštěč s kurzorem v dynamickém SQL, může nás to vyděsit (trojnásobná rána).

Plan guides a sp_prepare jsou na podobné lodi:kdybyste mě viděli používat jeden z nich, zvedli byste obočí; kdybyste mě viděli používat je společně, pravděpodobně byste mi zkontrolovali teplotu. Ale stejně jako kurzory, spouštěče a dynamické SQL mají své případy použití. A nedávno jsem narazil na scénář, kdy bylo jejich společné používání přínosné.

Pozadí

Máme spoustu dat. A spousta aplikací běžících proti těmto datům. Některé z těchto aplikací je obtížné nebo nemožné změnit, zejména běžné aplikace od třetí strany. Takže když jejich zkompilovaná aplikace odešle ad hoc dotazy na SQL Server, zejména jako připravený příkaz, a když nemáme volnost přidávat nebo měnit indexy, hned je několik příležitostí k ladění pryč.

V tomto případě jsme měli tabulku s několika miliony řádků. Zjednodušená a dezinfikovaná verze:

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;

Připravený výpis z aplikace vypadal takto (jak je vidět v mezipaměti plánu):

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

Problém je v tom, že pro některé hodnoty TypeID , bylo by mnoho tisíc řádků. Pro jiné hodnoty by jich bylo méně než 10. Pokud je na základě jednoho typu parametru zvolen (a znovu použit) nesprávný plán, může to být problém pro ostatní. U dotazu, který načítá několik řádků, chceme hledání indexu s vyhledáváním pro získání dalších nepokrytých sloupců, ale pro dotaz, který vrací 700 000 řádků, chceme pouze skenování seskupeného indexu. (V ideálním případě by rejstřík pokrýval, ale tato možnost tentokrát nebyla v kartách.)

V praxi aplikace vždy dostávala variaci skenování, i když to byla ta, která byla potřeba asi 1 % času. 99 % dotazů používalo prohledávání 2 milionů řádků, když mohli použít vyhledávání + 4 nebo 5 vyhledávání.

To bychom mohli snadno reprodukovat v Management Studio spuštěním tohoto dotazu:

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

Plány se vrátily takto:

Odhad byl v obou případech 1000 řádků; varování napravo jsou kvůli zbytkovým I/O.

Jak bychom se mohli ujistit, že dotaz zvolil správnou volbu v závislosti na parametru? Potřebovali bychom jej překompilovat, aniž bychom do dotazu přidávali nápovědu, zapínali příznaky trasování nebo měnili nastavení databáze.

Pokud jsem spustil dotazy nezávisle pomocí OPTION (RECOMPILE) , pokud je to vhodné, dostal bych vyhledávací dotaz:

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);

S RECOMPILE získáváme přesnější odhady a vyhledávání, když je potřebujeme.

Ale opět jsme nemohli přidat nápovědu k dotazu přímo.

Vyzkoušejte průvodce plánem

Spousta lidí varuje před plánovacími průvodci, ale tady jsme byli tak trochu v koutě. Určitě bychom raději změnili dotaz nebo indexy, pokud bychom mohli. Ale tohle může být další nejlepší věc.

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)';

Zdá se přímočaré; testovat to je problém. Jak nasimulujeme připravený výpis v Management Studiu? Jak si můžeme být jisti, že aplikace dostává řízený plán a že je to výslovně kvůli průvodci plánem?

Pokud se pokusíme simulovat tento dotaz v SSMS, bude to považováno za příkaz ad hoc, nikoli jako připravený příkaz, a nepodařilo se mi získat průvodce plánem:

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

Dynamické SQL také nefungovalo (také to bylo považováno za příkaz 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 to jsem nemohl udělat, protože by to také nezvedlo průvodce plánem (zde přebírá parametrizace a neměl jsem svobodu měnit nastavení databáze, i když by to mělo být považováno za připravený příkaz) :

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

Nemohu zkontrolovat mezipaměť plánu pro dotazy spouštěné z aplikace, protože plán uložený v mezipaměti neuvádí nic o použití průvodce plánem (SSMS za vás vloží tyto informace do XML, když generujete skutečný plán). A pokud dotaz skutečně sleduje nápovědu PŘEKOMPILOVAT, kterou předávám průvodci plánem, jak bych vůbec mohl někdy vidět nějaký důkaz v mezipaměti plánu?

Zkusme sp_prepare

Sp_prepare jsem ve své kariéře používal méně než průvodce plánem a nedoporučoval bych jej používat pro kód aplikace. (Jak zdůrazňuje Erik Darling, odhad lze získat z vektoru hustoty, nikoli z čichání parametru.)

V mém případě jej nechci používat z důvodu výkonu, chci ho (spolu s sp_execute) použít k simulaci připraveného příkazu přicházejícího z aplikace.

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 nám ukazuje, že v obou případech byl použit průvodce plánem.

Kvůli rekompilaci nebudete moci zkontrolovat mezipaměť plánu pro tyto výsledky. Ale ve scénáři, jako je ten můj, byste měli být schopni vidět účinky monitorování, explicitní kontroly prostřednictvím rozšířených událostí nebo pozorování zmírnění příznaku, který vás přiměl prozkoumat tento dotaz na prvním místě (jen si uvědomte, že průměrná doba běhu, dotaz statistiky atd. mohou být ovlivněny dodatečnou kompilací).

Závěr

Toto byl jeden případ, kdy byl průvodce plánem prospěšný a sp_prepare byl užitečný při ověřování, zda bude pro aplikaci fungovat. Ty nejsou často užitečné a méně často dohromady, ale pro mě to byla zajímavá kombinace. I bez průvodce plánem, pokud chcete použít SSMS k simulaci aplikace odesílající připravené výpisy, sp_prepare je váš přítel. (Viz také sp_prepexec, což může být zkratka, pokud se nepokoušíte ověřit dva různé plány pro stejný dotaz.)

Všimněte si, že toto cvičení nebylo nezbytně nutné k dosažení lepšího výkonu po celou dobu – šlo o vyrovnání rozdílů výkonu. Rekompilace samozřejmě nejsou zdarma, ale zaplatím malou pokutu za provedení 99 % mých dotazů za 250 ms a 1 % za 5 sekund, než abych se zasekl u plánu, který je naprosto hrozný pro 99 % dotazů. nebo 1 % dotazů.


  1. PERIOD_ADD() Příklady – MySQL

  2. Prokázání ekvivalence SQL dotazu

  3. Jaký je rozdíl mezi dočasnou tabulkou a proměnnou tabulky na serveru SQL?

  4. Chyba PostgreSQL:Vztah již existuje