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

Další argument pro uložené procedury

Toto je jedna z těch nábožensko-politických debat, které zuří už roky:mám používat uložené procedury, nebo mám do aplikace vkládat dotazy ad hoc? Vždy jsem byl zastáncem uložených procedur, a to z několika důvodů:

  1. Nemohu implementovat ochranu vkládání SQL, pokud je dotaz vytvořen v kódu aplikace. Vývojáři si mohou být vědomi parametrizovaných dotazů, ale nic je nenutí je správně používat.
  2. Nemohu vyladit dotaz, který je vložený do zdrojového kódu aplikace, ani nemohu prosadit žádné osvědčené postupy.
  3. Pokud najdu příležitost pro ladění dotazů, abych ji mohl nasadit, musím znovu zkompilovat a znovu nasadit kód aplikace, nikoli jen změnit uloženou proceduru.
  4. Pokud se dotaz používá na více místech v aplikaci nebo ve více aplikacích a vyžaduje změnu, musím jej změnit na více místech, zatímco u uložené procedury jej musím změnit pouze jednou (problémy s nasazením stranou).

Také vidím, že mnoho lidí zahazuje uložené procedury ve prospěch ORM. U jednoduchých aplikací to bude pravděpodobně v pořádku, ale jak se vaše aplikace stává složitější, pravděpodobně zjistíte, že váš ORM výběr prostě není schopen provádět určité vzory dotazů, což vás *nutí* používat uloženou proceduru. Pokud podporuje uložené procedury, to je.

I když všechny tyto argumenty stále považuji za docela přesvědčivé, nejsou to, o čem bych dnes chtěl mluvit; Chci mluvit o výkonu.

Mnoho argumentů tam bude jednoduše říkat:"Uložené procedury fungují lépe!" To mohlo být v určitém okamžiku okrajově pravdivé, ale protože SQL Server přidal možnost kompilace na úrovni příkazů spíše než na úrovni objektu a získal výkonné funkce, jako je optimize for ad hoc workloads , to už není moc pádný argument. Ladění indexu a rozumné vzory dotazů mají mnohem větší dopad na výkon než volba použití uložené procedury. pochybuji, že na moderních verzích najdete mnoho případů, kdy přesně stejný dotaz vykazuje znatelné rozdíly ve výkonu, pokud nezavádíte také další proměnné (například spuštění procedury lokálně vs. aplikace v jiném datovém centru na jiném kontinentu).

To znamená, že existuje aspekt výkonu, který je často přehlížen při řešení ad hoc dotazů:mezipaměť plánu. Můžeme použít optimize for ad hoc workloads abychom zabránili tomu, aby plány na jedno použití zaplnily naši mezipaměť (Kimberly Tripp (@KimberlyLTripp) na SQLskills.com o tom má několik skvělých informací zde), a to má vliv na plány na jedno použití bez ohledu na to, zda jsou dotazy spouštěny z uložené procedury nebo jsou provozovány ad hoc. Jiný dopad, kterého si bez ohledu na toto nastavení nemusíte všimnout, je, když je identický plány zabírají více slotů ve vyrovnávací paměti kvůli rozdílům v SET možnosti nebo menší delty ve skutečném textu dotazu. Celý fenomén „pomalá aplikace, rychlý SSMS“ pomohl mnoha lidem vyřešit problémy týkající se nastavení jako SET ARITHABORT . Dnes jsem chtěl mluvit o rozdílech v textu dotazu a ukázat něco, co lidi překvapí pokaždé, když to vytáhnu.

Mezipaměť k vypálení

Řekněme, že máme velmi jednoduchý systém, na kterém běží AdventureWorks2012. A abychom dokázali, že to nepomáhá, povolili jsme optimize for ad hoc workloads :

EXEC sp_configure 'zobrazit pokročilé možnosti', 1;GORECONFIGURE WITH OVERRIDE;GOEXEC sp_configure 'optimalizovat pro zátěže ad hoc', 1;GORECONFIGURE WITH OVERRIDE;

A pak uvolněte mezipaměť plánu:

DBCC FREEPROCCACHE;

Nyní vygenerujeme několik jednoduchých variant dotazu, který je jinak identický. Tyto varianty mohou potenciálně představovat styly kódování pro dva různé vývojáře – drobné rozdíly v mezerách, velkých/malých písmenech atd.

SELECT TOP (1) SalesOrderID, OrderDate, SubTotalFROM Sales.SalesOrderHeaderWHERE SalesOrderID>=75120ORDER BY OrderDate DESC;GO -- změna>=75120 na> 75119 (stejná logika, protože je to (1)JÍDLO prodeje SELECTO TOP DateDate, SubTotalFROM Sales.SalesOrderHeaderWHERE SalesOrderID> 75119ORDER BY OrderDate DESC;GO -- změňte dotaz na všechna malá písmenaGO vyberte top (1) salesorderid, orderdate, subtotalfrom sales.salesorderheaderwhere salesorderid around remove the parent19orderid (odstranit datum nadřazené objednávky; GO objednávky podle objednávky) argument pro topGO select top 1 salesorderid, orderdate, subtotalfrom sales.salesorderheaderwhere salesorderid> 75119order by orderdate desc;GO -- přidejte mezeru za top 1GO select top 1 salesorderid, orderdate, subtotalfrom sales.salesorderheaderwhere salesorderideid> 75119order by orderdate desc;GO -- odstraňte mezery mezi čárkamiGO select top 1 salesorderid,orderdate,subtotalfrom sales.salesorderheaderwhere salesorderid> 75119order by orderdate desc;GO 

Pokud spustíme tuto dávku jednou a poté zkontrolujeme mezipaměť plánu, uvidíme, že máme 6 kopií v podstatě přesně stejného plánu provádění. Důvodem je, že text dotazu je binárně hašovaný, což znamená, že velká a malá písmena a mezery mají rozdíl a mohou jinak identické dotazy vypadat jako jedinečné pro SQL Server.

SELECT [text], size_in_bytes, usecounts, cacheobjtypeFROM sys.dm_exec_cached_plans JAKO pCROSS APPLY sys.dm_exec_sql_text(p.plan_handle) JAKO tWHERE LOWER(t.[text]) LIKE '%ales'orhead%'sales'%ales'orhead.sales 

Výsledky:

text size_in_bytes použití cacheobjtype
vyberte 1 nejlepší ID objednávky,o… 272 1 Zkompilovaný útržek plánu
vyberte 1 nejlepší ID objednávky, … 272 1 Zkompilovaný útržek plánu
vyberte top 1 ID objednávky, o… 272 1 Zkompilovaný útržek plánu
vyberte top (1) salesorderid,… 272 1 Zkompilovaný útržek plánu
VYBRAT TOP (1) SalesOrderID,… 272 1 Zkompilovaný útržek plánu
VYBRAT TOP (1) SalesOrderID,… 272 1 Zkompilovaný útržek plánu

Výsledky po prvním provedení "identických" dotazů

Takže to není úplně plýtvání, protože nastavení ad hoc umožnilo SQL Serveru ukládat pouze malé útržky při prvním spuštění. Pokud však dávku spustíme znovu (bez uvolnění mezipaměti procedur), uvidíme trochu alarmující výsledek:

text size_in_bytes použití cacheobjtype
vyberte 1 nejlepší ID objednávky,o… 49 152 1 Zkompilovaný plán
vyberte 1 nejlepší ID objednávky, … 49 152 1 Zkompilovaný plán
vyberte top 1 ID objednávky, o… 49 152 1 Zkompilovaný plán
vyberte top (1) salesorderid,… 49 152 1 Zkompilovaný plán
VYBRAT TOP (1) SalesOrderID,… 49 152 1 Zkompilovaný plán
VYBRAT TOP (1) SalesOrderID,… 49 152 1 Zkompilovaný plán

Výsledky po druhém provedení "identických" dotazů

Totéž se děje pro parametrizované dotazy, bez ohledu na to, zda je parametrizace jednoduchá nebo vynucená. A totéž se stane, když není povoleno nastavení ad hoc, až na to, že se to stane dříve.

Čistým výsledkem je, že to může způsobit velké nafouknutí mezipaměti plánu, a to i u dotazů, které vypadají identicky – až po dva dotazy, kde jeden vývojář odsadí tabulátor a druhý odsadí 4 mezery. Nemusím vám říkat, že snažit se prosadit tento typ konzistence v týmu může být únavné až nemožné. Takže v mé mysli to dává silný souhlas modularizaci, ustupování DRY a centralizaci tohoto typu dotazu do jediné uložené procedury.

Upozornění

Samozřejmě, pokud umístíte tento dotaz do uložené procedury, budete mít pouze jednu jeho kopii, takže se zcela vyhnete možnosti mít více verzí dotazu s mírně odlišným textem dotazu. Nyní byste také mohli namítnout, že různí uživatelé mohou vytvořit stejnou uloženou proceduru s různými názvy a v každé uložené proceduře existuje malá obměna textu dotazu. I když je to možné, myslím, že to představuje úplně jiný problém. :-)


  1. Stanovení a identifikace cílů řádků v prováděcích plánech

  2. OracleDataSource vs. Oracle UCP PoolDataSource

  3. Jak zřetězit text z více řádků do jednoho textového řetězce na serveru SQL Server

  4. Jak TO_SECONDS() funguje v MariaDB