Dit is een van die religieuze/politieke debatten die al jaren woedt:moet ik opgeslagen procedures gebruiken of moet ik ad-hocvragen in mijn applicatie plaatsen? Ik ben altijd een voorstander geweest van opgeslagen procedures, om een paar redenen:
- Ik kan geen beveiligingen voor SQL-injectie implementeren als de query in de toepassingscode is gemaakt. De ontwikkelaars zijn misschien op de hoogte van geparametriseerde zoekopdrachten, maar niets dwingt hen om ze correct te gebruiken.
- Ik kan een query die is ingesloten in de broncode van een toepassing niet afstemmen, en ik kan ook geen best practices afdwingen.
- Als ik een mogelijkheid vind voor het afstemmen van query's, moet ik de applicatiecode opnieuw compileren en implementeren om deze te kunnen implementeren, in plaats van alleen de opgeslagen procedure te wijzigen.
- Als de query op meerdere plaatsen in de applicatie of in meerdere applicaties wordt gebruikt en deze moet worden gewijzigd, moet ik deze op meerdere plaatsen wijzigen, terwijl ik deze bij een opgeslagen procedure maar één keer hoef te wijzigen (implementatieproblemen opzij).
Ik zie ook dat veel mensen opgeslagen procedures overboord gooien ten gunste van ORM's. Voor eenvoudige toepassingen zal dit waarschijnlijk goed gaan, maar naarmate uw toepassing complexer wordt, zult u waarschijnlijk ontdekken dat uw ORM naar keuze gewoon niet in staat is om bepaalde zoekpatronen uit te voeren, *dwingt* u om een opgeslagen procedure te gebruiken. Als het opgeslagen procedures ondersteunt, tenminste.
Hoewel ik al deze argumenten nog steeds behoorlijk overtuigend vind, zijn ze niet waar ik het vandaag over wil hebben; Ik wil het hebben over prestaties.
Veel argumenten zullen simpelweg zeggen:"opgeslagen procedures presteren beter!" Dat was op een gegeven moment misschien marginaal waar, maar sinds SQL Server de mogelijkheid heeft toegevoegd om op instructieniveau te compileren in plaats van op objectniveau, en krachtige functionaliteit heeft gekregen, zoals optimize for ad hoc workloads
, is dit niet langer een erg sterk argument. Indexafstemming en verstandige querypatronen hebben een veel grotere invloed op de prestaties dan ooit zal kiezen voor het gebruik van een opgeslagen procedure; ik betwijfel of je in moderne versies veel gevallen zult aantreffen waarin exact dezelfde query merkbare prestatieverschillen vertoont, tenzij je ook andere variabelen introduceert (zoals het lokaal uitvoeren van een procedure versus een applicatie in een ander datacenter op een ander continent).
Dat gezegd hebbende, is er een prestatieaspect dat vaak over het hoofd wordt gezien bij het omgaan met ad-hocvragen:de plancache. We kunnen optimize for ad hoc workloads
om te voorkomen dat plannen voor eenmalig gebruik onze cache vullen (Kimberly Tripp (@KimberlyLTripp) van SQLskills.com heeft hier geweldige informatie over), en dat is van invloed op plannen voor eenmalig gebruik, ongeacht of de query's worden uitgevoerd vanuit een opgeslagen procedure of worden ad hoc uitgevoerd. Een ander effect dat u misschien niet opmerkt, ongeacht deze instelling, is wanneer identiek plannen nemen meerdere slots in de cache in beslag vanwege verschillen in SET
opties of kleine delta's in de eigenlijke vraagtekst. Het hele fenomeen "slow in the application, fast in SSMS" heeft veel mensen geholpen bij het oplossen van problemen met instellingen zoals SET ARITHABORT
. Vandaag wilde ik het hebben over verschillen in vraagtekst en iets demonstreren dat mensen elke keer weer verrast als ik het ter sprake breng.
Cache om te branden
Laten we zeggen dat we een heel eenvoudig systeem hebben waarop AdventureWorks2012 draait. En om te bewijzen dat het niet helpt, hebben we optimize for ad hoc workloads
ingeschakeld :
EXEC sp_configure 'show advanced options', 1; GO RECONFIGURE WITH OVERRIDE; GO EXEC sp_configure 'optimize for ad hoc workloads', 1; GO RECONFIGURE WITH OVERRIDE;
En maak dan de plancache vrij:
DBCC FREEPROCCACHE;
Nu genereren we een paar eenvoudige variaties op een zoekopdracht die verder identiek is. Deze variaties kunnen mogelijk codeerstijlen voor twee verschillende ontwikkelaars vertegenwoordigen - kleine verschillen in witruimte, hoofdletters/kleine letters, enz.
SELECT TOP (1) SalesOrderID, OrderDate, SubTotal FROM Sales.SalesOrderHeader WHERE SalesOrderID >= 75120 ORDER BY OrderDate DESC; GO -- change >= 75120 to > 75119 (same logic since it's an INT) GO SELECT TOP (1) SalesOrderID, OrderDate, SubTotal FROM Sales.SalesOrderHeader WHERE SalesOrderID > 75119 ORDER BY OrderDate DESC; GO -- change the query to all lower case GO select top (1) salesorderid, orderdate, subtotal from sales.salesorderheader where salesorderid > 75119 order by orderdate desc; GO -- remove the parentheses around the argument for top GO select top 1 salesorderid, orderdate, subtotal from sales.salesorderheader where salesorderid > 75119 order by orderdate desc; GO -- add a space after top 1 GO select top 1 salesorderid, orderdate, subtotal from sales.salesorderheader where salesorderid > 75119 order by orderdate desc; GO -- remove the spaces between the commas GO select top 1 salesorderid,orderdate,subtotal from sales.salesorderheader where salesorderid > 75119 order by orderdate desc; GO
Als we die batch één keer uitvoeren en vervolgens de plancache controleren, zien we dat we 6 exemplaren hebben van in wezen exact hetzelfde uitvoeringsplan. Dit komt omdat de querytekst binair gehasht is, wat betekent dat hoofdletters en witruimten een verschil maken en ervoor kunnen zorgen dat anderszins identieke query's er uniek uitzien voor SQL Server.
SELECT [text], size_in_bytes, usecounts, cacheobjtype FROM sys.dm_exec_cached_plans AS p CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t WHERE LOWER(t.[text]) LIKE '%ales.sales'+'orderheader%';
Resultaten:
tekst | size_in_bytes | gebruiktellingen | cacheobjtype |
---|---|---|---|
selecteer top 1 verkooporder-ID,o… | 272 | 1 | Gecompileerde planstrook |
selecteer top 1 verkooporder-ID, … | 272 | 1 | Gecompileerde planstrook |
selecteer top 1 verkooporder-ID, o… | 272 | 1 | Gecompileerde planstrook |
selecteer top (1) salesorderid,… | 272 | 1 | Gecompileerde planstrook |
SELECTEER TOP (1) SalesOrderID,… | 272 | 1 | Gecompileerde planstrook |
SELECTEER TOP (1) SalesOrderID,… | 272 | 1 | Gecompileerde planstrook |
Resultaten na eerste uitvoering van "identieke" zoekopdrachten
Dit is dus niet helemaal verspillend, aangezien de ad-hocinstelling SQL Server in staat heeft gesteld om alleen kleine stubs op te slaan bij de eerste uitvoering. Als we de batch echter opnieuw uitvoeren (zonder de procedurecache vrij te maken), zien we een iets alarmerender resultaat:
tekst | size_in_bytes | gebruiktellingen | cacheobjtype |
---|---|---|---|
selecteer top 1 verkooporder-ID,o… | 49.152 | 1 | Samengesteld plan |
selecteer top 1 verkooporder-ID, … | 49.152 | 1 | Samengesteld plan |
selecteer top 1 verkooporder-ID, o… | 49.152 | 1 | Samengesteld plan |
selecteer top (1) salesorderid,… | 49.152 | 1 | Samengesteld plan |
SELECTEER TOP (1) SalesOrderID,… | 49.152 | 1 | Samengesteld plan |
SELECTEER TOP (1) SalesOrderID,… | 49.152 | 1 | Samengesteld plan |
Resultaten na tweede uitvoering van "identieke" zoekopdrachten
Hetzelfde gebeurt voor geparametriseerde query's, ongeacht of de parametrering eenvoudig of geforceerd is. En hetzelfde gebeurt als de ad-hocinstelling niet is ingeschakeld, behalve dat het eerder gebeurt.
Het netto resultaat is dat dit veel plancache-bloat kan veroorzaken, zelfs voor zoekopdrachten die er identiek uitzien - helemaal tot twee zoekopdrachten waarbij de ene ontwikkelaar inspringt met een tabblad en de andere inspringt met 4 spaties. Ik hoef je niet te vertellen dat het proberen om dit soort consistentie in een team af te dwingen, van vervelend tot onmogelijk kan zijn. Dus in mijn gedachten geeft dit een sterke knipoog naar modularisering, toegeven aan DRY en het centraliseren van dit type query in een enkele opgeslagen procedure.
Een waarschuwing
Als u deze query in een opgeslagen procedure plaatst, heeft u er natuurlijk maar één exemplaar van, dus u vermijdt volledig het potentieel voor meerdere versies van de query met een iets andere querytekst. Nu zou je ook kunnen stellen dat verschillende gebruikers dezelfde opgeslagen procedure met verschillende namen kunnen maken, en in elke opgeslagen procedure is er een kleine variatie in de vraagtekst. Hoewel het mogelijk is, denk ik dat dat een heel ander probleem is. :-)