sql >> Database teknologi >  >> RDS >> Database

Endnu et argument for lagrede procedurer

Dette er en af ​​de religiøse/politiske debatter, der har raset i årevis:skal jeg bruge lagrede procedurer, eller skal jeg indsætte ad hoc-forespørgsler i min ansøgning? Jeg har altid været tilhænger af lagrede procedurer af et par grunde:

  1. Jeg kan ikke implementere SQL-injektionsbeskyttelse, hvis forespørgslen er konstrueret i applikationskoden. Udviklerne kan være opmærksomme på parametriserede forespørgsler, men intet tvinger dem til at bruge dem korrekt.
  2. Jeg kan ikke justere en forespørgsel, der er indlejret i applikationens kildekode, og jeg kan heller ikke håndhæve nogen bedste praksis.
  3. Hvis jeg finder en mulighed for justering af forespørgsler, for at kunne implementere den, skal jeg genkompilere og geninstallere applikationskoden, i modsætning til blot at ændre den lagrede procedure.
  4. Hvis forespørgslen bruges flere steder i applikationen eller i flere applikationer, og den kræver en ændring, er jeg nødt til at ændre den flere steder, hvorimod jeg med en lagret procedure kun skal ændre den én gang (implementeringsproblemer til side).

Jeg kan også se, at mange mennesker dropper lagrede procedurer til fordel for ORM'er. For simple applikationer vil dette sandsynligvis gå okay, men efterhånden som din applikation bliver mere kompleks, vil du sandsynligvis opdage, at din foretrukne ORM simpelthen ikke er i stand til at udføre bestemte forespørgselsmønstre, og *tvinger* dig til at bruge en lagret procedure. Hvis det understøtter lagrede procedurer, dvs.

Selvom jeg stadig finder alle disse argumenter ret overbevisende, er de ikke det, jeg vil tale om i dag; Jeg vil gerne tale om ydeevne.

En masse argumenter derude vil simpelthen sige, "lagrede procedurer fungerer bedre!" Det kan have været marginalt sandt på et tidspunkt, men siden SQL Server tilføjede evnen til at kompilere på sætningsniveau i stedet for objektniveau, og har fået kraftfuld funktionalitet som optimize for ad hoc workloads , dette er ikke længere et særligt stærkt argument. Indeksjustering og fornuftige forespørgselsmønstre har en meget større indflydelse på ydeevnen, end hvis man vælger at bruge en lagret procedure nogensinde. på moderne versioner tvivler jeg på, at du vil finde mange tilfælde, hvor nøjagtig den samme forespørgsel udviser mærkbare præstationsforskelle, medmindre du også introducerer andre variabler (såsom at køre en procedure lokalt vs. en applikation i et andet datacenter på et andet kontinent).

Når det er sagt, er der et ydeevneaspekt, som ofte overses, når man håndterer ad hoc-forespørgsler:plancachen. Vi kan bruge optimize for ad hoc workloads for at forhindre engangsplaner i at fylde vores cache op (Kimberly Tripp (@KimberlyLTripp) fra SQLskills.com har nogle gode oplysninger om dette her), og det påvirker engangsplaner, uanset om forespørgslerne køres fra en lagret procedure eller køres ad hoc. En anden effekt, du måske ikke bemærker, uanset denne indstilling, er, når den er identisk planer optager flere pladser i cachen på grund af forskelle i SET muligheder eller mindre deltas i selve forespørgselsteksten. Hele fænomenet "langsomt i applikationen, hurtigt i SSMS" har hjulpet mange mennesker med at løse problemer, der involverer indstillinger som SET ARITHABORT . I dag ville jeg tale om forskelle i forespørgselstekster og demonstrere noget, der overrasker folk, hver gang jeg tager det op.

Cache til brænding

Lad os sige, at vi har et meget simpelt system, der kører AdventureWorks2012. Og bare for at bevise, at det ikke hjælper, har vi aktiveret optimize for ad hoc workloads :

EXEC sp_configure 'vis avancerede indstillinger', 1;GORECONFIGURE WITH OVERRIDE;GOEXEC sp_configure 'optimize for ad hoc workloads', 1;GORECONFIGURE WITH OVERRIDE;

Og frigør derefter planens cache:

DBCC FREEPROCCACHE;

Nu genererer vi et par simple variationer af en forespørgsel, der ellers er identisk. Disse variationer kan potentielt repræsentere kodningsstile for to forskellige udviklere – små forskelle i hvidt mellemrum, store/små bogstaver osv.

SELECT TOP (1) SalesOrderID, OrderDate, SubTotalFROM Sales.SalesOrderHeaderWHERE SalesOrderID>=75120ORDER BY OrderDate DESC;GO -- skift>=75120 til> 75119 (samme logik, da det er et INT)GO SELECT TOPID ( OrderDate, SubTotalFROM Sales.SalesOrderHeader WHERE SalesOrderID> 75119ORDER BY OrderDate DESC;GO -- skift forespørgslen til alle små bogstaver argumentet for topGO vælg top 1 salgsordreid, ordredato, subtotal fra salg.salgsorderheaderhvor salgsordreid> 75119ordre by orderdate desc;GO -- tilføj et mellemrum efter top 1GO vælg top 1 salgsordreid, ordredato, subtotal fra salg.salgsorderheaderhvor salgsordreordreid> 7511 afordreordrec; -- fjern mellemrummene mellem kommaerneGO vælg top 1 salgsordre-id, ordredato, subtotal fra salg.salgsordreoverskrift hvor salgsordre-id> 75119ordre efter ordredato desc;GO 

Hvis vi kører den batch én gang og derefter tjekker planens cache, ser vi, at vi har 6 kopier af i det væsentlige nøjagtig den samme udførelsesplan. Dette skyldes, at forespørgselsteksten er binær hash, hvilket betyder, at store og små bogstaver og mellemrum gør en forskel og kan få ellers identiske forespørgsler til at se unikke ud for SQL Server.

VÆLG [text], size_in_bytes, usecounts, cacheobjtypeFROM sys.dm_exec_cached_plans AS pCROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS tWHERE LOWER(t.[text]) LIKE '%ales''.saleser'%+; 

Resultater:

tekst størrelse_i_bytes brugetal cacheobjtype
vælg top 1 salgsordre-id, o... 272 1 Samlet planstub
vælg top 1 salgsordre-id, … 272 1 Samlet planstub
vælg top 1 salgsordre-id, o... 272 1 Samlet planstub
vælg top (1) salgsordre-id,... 272 1 Samlet planstub
VÆLG TOP (1) SalesOrderID,... 272 1 Samlet planstub
VÆLG TOP (1) SalesOrderID,... 272 1 Samlet planstub

Resultater efter første udførelse af "identiske" forespørgsler

Så dette er ikke helt spild, da ad hoc-indstillingen har tilladt SQL Server kun at gemme små stubs ved første udførelse. Hvis vi dog kører batchen igen (uden at frigøre procedurecachen), ser vi et lidt mere alarmerende resultat:

tekst størrelse_i_bytes brugetal cacheobjtype
vælg top 1 salgsordre-id, o... 49.152 1 Samlet plan
vælg top 1 salgsordre-id, … 49.152 1 Samlet plan
vælg top 1 salgsordre-id, o... 49.152 1 Samlet plan
vælg top (1) salgsordre-id,... 49.152 1 Samlet plan
VÆLG TOP (1) SalesOrderID,... 49.152 1 Samlet plan
VÆLG TOP (1) SalesOrderID,... 49.152 1 Samlet plan

Resultater efter anden udførelse af "identiske" forespørgsler

Det samme sker for parametrerede forespørgsler, uanset om parametreringen er enkel eller tvungen. Og det samme sker, når ad hoc-indstillingen ikke er aktiveret, bortset fra at det sker før.

Nettoresultatet er, at dette kan give en masse plan-cache-bloat, selv for forespørgsler, der ser identiske ud – helt ned til to forespørgsler, hvor den ene udvikler indrykker med en tabulator og den anden indrykker med 4 mellemrum. Jeg behøver ikke fortælle dig, at det kan være alt fra kedeligt til umuligt at forsøge at håndhæve denne type konsistens på tværs af et team. Så efter min mening giver dette et stærkt nik til at modularisere, give efter for DRY og centralisere denne type forespørgsler i en enkelt lagret procedure.

En advarsel

Selvfølgelig, hvis du placerer denne forespørgsel i en lagret procedure, vil du kun have én kopi af den, så du helt undgår potentialet for at have flere versioner af forespørgslen med lidt forskellig forespørgselstekst. Nu kan du også argumentere for, at forskellige brugere kan oprette den samme lagrede procedure med forskellige navne, og i hver lagret procedure er der en lille variation af forespørgselsteksten. Selvom det er muligt, tror jeg, det repræsenterer et helt andet problem. :-)


  1. Sådan bruger du Coalesce-funktionen i Oracle

  2. varchar2(n BYTE|CHAR) standard -> CHAR eller BYTE

  3. RR vs YY i Oracle

  4. MariaDB JSON-funktioner