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

Případ Cardinality Odhad Red Herring

Pracujete s vývojářem, který hlásí pomalý výkon pro následující volání uložené procedury:

EXEC [dbo].[charge_by_date] '2/28/2013';

Ptáte se, jaký problém vývojář vidí, ale jediná další informace, kterou uslyšíte, je, že „běží pomalu“. Takže skočíte na instanci SQL Server a podíváte se na aktuální exekuční plán. Děláte to, protože vás zajímá nejen to, jak vypadá plán provádění, ale také jaký je odhadovaný a skutečný počet řádků plánu:

Nejprve se podíváte na operátory plánu a uvidíte několik pozoruhodných detailů:

  • V kořenovém operátoru je varování
  • Existuje prohledání tabulek pro obě tabulky, na které se odkazuje na úrovni listu (charge_jan a charge_feb) a vy se divíte, proč jsou obě stále hromady a nemají seskupené indexy
  • Vidíte, že tabulkou charge_feb procházejí pouze řádky, nikoli tabulkou charge_jan
  • V plánu vidíte paralelní zóny

Pokud jde o varování v kořenovém iterátoru, najedete na něj a uvidíte, že chybí varování indexu s doporučením pro následující indexy:

CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[charge_feb] ([charge_dt])
INCLUDE ([charge_no])
GO
 
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[charge_jan] ([charge_dt])
INCLUDE ([charge_no])
GO

Zeptáte se původního vývojáře databáze, proč neexistuje seskupený index, a odpověď je „Nevím.“

Pokračujte ve vyšetřování před provedením jakýchkoli změn a podíváte se na kartu Plan Tree v SQL Sentry Plan Explorer a skutečně uvidíte, že mezi odhadovanými a skutečnými řádky jedné z tabulek jsou značné odchylky:

Zdá se, že existují dva problémy:

  • Podhodnocený odhad pro řádky v prohledání tabulky charge_jan
  • Nadhodnocení pro řádky v prohledání tabulky charge_feb

Takže odhady mohutnostijsou zkosený a přemýšlíte, jestli to souvisí s čicháním parametrů. Rozhodnete se zkontrolovat zkompilovanou hodnotu parametru a porovnat ji s hodnotou parametru za běhu, kterou můžete vidět na kartě Parametry:

Skutečně existují rozdíly mezi hodnotou za běhu a zkompilovanou hodnotou. Zkopírujete databázi do testovacího prostředí podobného produktu a poté otestujete provedení uložené procedury s hodnotou runtime 2/28/2013 a poté 31/1/2013.

Plány z 28. 2. 2013 a 31. 1. 2013 mají identické tvary, ale odlišné skutečné datové toky. Plán a odhady mohutnosti k 28. 2. 2013 byly následující:

A zatímco plán 28. 2. 2013 nevykazuje žádný problém s odhadem mohutnosti, plán 31. 1. 2013 ano:

Takže druhý plán ukazuje totéž nad a podhodnocením, jen obráceně oproti původnímu plánu, na který jste se dívali.

Rozhodnete se přidat navrhované indexy do testovacího prostředí podobného prod pro tabulky charge_jan i charge_feb a uvidíte, zda to vůbec pomůže. Po provedení uložených procedur v pořadí leden/únor uvidíte následující nové tvary plánu a související odhady mohutnosti:

Nový plán používá operaci Index Seek z každé tabulky, ale stále vidíte, že z jedné tabulky plyne nula řádků a z druhé ne, a stále vidíte zkreslení odhadu mohutnosti na základě sniffování parametrů, když je hodnota za běhu v jiném měsíci od kompilace. časová hodnota.

Váš tým má zásadu nepřidávat indexy bez prokázání dostatečného přínosu a souvisejícího regresního testování. Rozhodnete se prozatím odstranit neklastrované indexy, které jste právě vytvořili. Zatímco chybějící seskupené hned neřešíte index, rozhodnete se, že se o to postaráte později.

V tomto okamžiku si uvědomíte, že se musíte dále podívat na definici uložené procedury, která je následující:

CREATE PROCEDURE dbo.charge_by_date
  @charge_dt datetime
AS
  SELECT charge_no
  FROM dbo.charge_view
  WHERE charge_dt = @charge_dt
GO

Dále se podíváte na definici objektu charge_view:

CREATE VIEW charge_view
AS
  SELECT *
  FROM [charge_jan]
  UNION ALL
  SELECT *
  FROM [charge_feb]
GO

Zobrazení odkazuje na údaje o poplatcích, které jsou rozděleny do různých tabulek podle data. A pak se ptáte, zda lze zabránit zkreslení plánu provádění druhého dotazu změnou definice uložené procedury.

Pokud bude optimalizátor za běhu vědět, jaká je hodnota, problém s odhadem mohutnosti zmizí a zlepší se celkový výkon?

Pokračujte a předefinujte volání uložené procedury následovně a přidejte nápovědu RECOMPILE (s vědomím, že jste také slyšeli, že to může zvýšit využití CPU, ale protože se jedná o testovací prostředí, můžete to zkusit):

ALTER PROCEDURE charge_by_date
  @charge_dt datetime
AS
  SELECT charge_no
  FROM dbo.charge_view
  WHERE charge_dt = @charge_dt
  OPTION (RECOMPILE);
GO

Poté znovu spustíte uloženou proceduru pomocí hodnoty 31. 1. 2013 a poté hodnoty 28. 2. 2013.

Tvar plánu zůstává stejný, ale problém s odhadem mohutnosti je nyní odstraněn.

Údaje odhadu mohutnosti k 31. 1. 2013 ukazují:

A údaje o odhadu mohutnosti z 28. 2. 2013 ukazují:

To vás na okamžik potěší, ale pak si uvědomíte, že trvání celkového provádění dotazu se zdá relativně stejné jako předtím. Začnete pochybovat, že vývojář bude s vašimi výsledky spokojený. Vyřešili jste zkreslení odhadu mohutnosti, ale bez očekávaného zvýšení výkonu si nejste jisti, zda jste pomohli nějakým smysluplným způsobem.

Právě v tomto okamžiku si uvědomíte, že plán provádění dotazu je pouze podmnožinou informací, které byste mohli potřebovat, a tak své prozkoumání dále rozšíříte pohledem na kartu Tabulka I/O. Zobrazí se následující výstup pro provedení 31. 1. 2013:

A pro provedení 28.2.2013 vidíte podobná data:

V tu chvíli vás zajímá, zda operace přístupu k datům pro obě tabulky jsou nezbytné v každém plánu. Pokud optimalizátor ví, že potřebujete pouze lednové řádky, proč vůbec přistupovat k únoru a naopak? Pamatujte také, že optimalizátor dotazů nemá žádné záruky, že neexistují skutečné řádky z ostatních měsíců ve „špatné“ tabulce, pokud takové záruky nebyly poskytnuty výslovně prostřednictvím omezení v tabulce samotné.

Zkontrolujete definice tabulek pomocí sp_help pro každou tabulku a nevidíte žádná omezení definovaná pro žádnou tabulku.

Takže jako test přidáte následující dvě omezení:

ALTER TABLE [dbo].[charge_jan]
  ADD CONSTRAINT charge_jan_chk CHECK
  (charge_dt >= '1/1/2013' AND charge_dt < '2/1/2013');
GO
 
ALTER TABLE [dbo].[charge_feb]
  ADD CONSTRAINT charge_feb_chk CHECK
  (charge_dt >= '2/1/2013' AND charge_dt < '3/1/2013');
GO

Znovu spustíte uložené procedury a uvidíte následující tvary plánu a odhady mohutnosti.

31.1.2013 provedení:

28.2.2013 provedení:

Když se znovu podíváte na tabulku I/O, uvidíte následující výstup pro provedení 31. ledna 2013:

A pro provedení 2/28/2013 vidíte podobná data, ale pro tabulku charge_feb:

Pamatujte si, že máte REKOMPILOVAT stále v definici uložené procedury, pokusíte se ji odebrat a uvidíte, zda uvidíte stejný efekt. Po provedení tohoto postupu uvidíte návrat přístupu ke dvěma tabulkám, ale bez skutečných logických čtení pro tabulku, která v ní nemá žádné řádky (ve srovnání s původním plánem bez omezení). Například provedení 31. 1. 2013 ukázalo následující I/O výstup tabulky:

Rozhodnete se pokročit v zátěžovém testování nových omezení CHECK a RECOMPILE, přičemž zcela odstraníte přístup k tabulce z plánu (a souvisejících operátorů plánu). Připravte se také na debatu o seskupeném indexovém klíči a vhodném podpůrném neklastrovaném indexu, který pojme širší sadu úloh, které aktuálně přistupují k přidruženým tabulkám.


  1. Funkce LAST_DAY() v Oracle

  2. Včetně tabulek a schémat při výpisu sloupců identity v databázi SQL Server

  3. VYTVOŘIT JAZYK plpython3u – PostgreSQL 9.6

  4. Přesun do MariaDB Backup