Jag har skrivit flera gånger om hur man använder markörer och hur det i de flesta fall är mer effektivt att skriva om sina markörer med hjälp av set-baserad logik.
Jag är dock realistisk.
Jag vet att det finns fall där markörer är "obligatoriska" – du måste anropa en annan lagrad procedur eller skicka ett e-postmeddelande för varje rad, du utför underhållsuppgifter mot varje databas, eller du kör en engångsuppgift som helt enkelt är inte värt att lägga tid på att konvertera till set-baserat.
Hur du (förmodligen) gör idag
Oavsett anledningen till att du fortfarande använder markörer, bör du åtminstone vara försiktig så att du inte använder de ganska dyra standardalternativen. De flesta börjar sina markörer så här:
DECLARE c CURSOR FOR SELECT whatever FROM ...
Nu igen, för ad-hoc, enstaka uppgifter, är detta förmodligen bara bra. Men det finns...
Andra sätt att göra det
Jag ville köra några tester med standardinställningarna och jämföra dem med olika marköralternativ som LOKALT
, STATISK
, READ_ONLY
och FAST_FORWARD
. (Det finns massor av alternativ, men det här är de som används oftast eftersom de är tillämpliga på de vanligaste typerna av marköroperationer som människor använder.) Jag ville inte bara testa råhastigheten för några olika kombinationer, utan även påverkan på tempdb och minne, både efter en kall serviceomstart och med en varm cache.
Frågan jag bestämde mig för att mata till markören är en mycket enkel fråga mot sys.objects
, i exempeldatabasen AdventureWorks2012. Detta returnerar 318 500 rader på mitt system (ett mycket ödmjukt 2-kärnigt system med 4 GB RAM):
SELECT c1.[object_id] FROM sys.objects AS c1 CROSS JOIN (SELECT TOP 500 name FROM sys.objects) AS c2;
Sedan slog jag in den här frågan i en markör med olika alternativ (inklusive standardinställningarna) och körde några tester, mätte totalt serverminne, sidor allokerade till tempdb (enligt sys.dm_db_task_space_usage
och/eller sys.dm_db_session_space_usage
), och total varaktighet. Jag försökte också observera tempdb-påståenden med hjälp av skript från Glenn Berry och Robert Davis, men på mitt ynka system kunde jag inte upptäcka några som helst påståenden. Naturligtvis är jag också på SSD och absolut inget annat körs på systemet, så det här kan vara saker du vill lägga till i dina egna tester om tempdb är mer sannolikt att vara en flaskhals.
Så till slut såg frågorna ut ungefär så här, med diagnostiska frågor som peppade in vid lämpliga punkter:
DECLARE @i INT = 1; DECLARE c CURSOR -- LOCAL -- LOCAL STATIC -- LOCAL FAST_FORWARD -- LOCAL STATIC READ_ONLY FORWARD_ONLY FOR SELECT c1.[object_id] FROM sys.objects AS c1 CROSS JOIN (SELECT TOP 500 name FROM sys.objects) AS c2 ORDER BY c1.[object_id]; OPEN c; FETCH c INTO @i; WHILE (@@FETCH_STATUS = 0) BEGIN SET @i += 1; -- meaningless operation FETCH c INTO @i; END CLOSE c; DEALLOCATE c;
Resultat
Längd
Den absolut viktigaste och vanligaste åtgärden är "hur lång tid tog det?" Tja, det tog nästan fem gånger så lång tid att köra en markör med standardalternativen (eller med bara LOCAL
specificerad), jämfört med att ange antingen STATIC
eller FAST_FORWARD
:
Minne
Jag ville också mäta det extra minne som SQL Server skulle begära när man uppfyller varje markörtyp. Så jag startade helt enkelt om före varje kall cache-test, och mätte prestandaräknaren Totalt serverminne (KB)
före och efter varje test. Den bästa kombinationen här var LOCAL FAST_FORWARD
:
tempdb-användning
Detta resultat överraskade för mig. Eftersom definitionen av en statisk markör innebär att den kopierar hela resultatet till tempdb, och det uttrycks faktiskt i sys.dm_exec_cursors
som SNAPSHOT
, jag förväntade mig att träffen på tempdb-sidor skulle vara högre med alla statiska varianter av markören. Så var inte fallet; återigen ser vi en ungefär 5X träff på tempdb-användning med standardmarkören och den med bara LOCAL
specificerat:
Slutsats
I flera år har jag betonat att följande alternativ alltid bör anges för dina markörer:
LOCAL STATIC READ_ONLY FORWARD_ONLY
Från och med denna tidpunkt, tills jag har en chans att testa ytterligare permutationer eller hitta fall där det inte är det snabbaste alternativet, kommer jag att rekommendera följande:
LOCAL FAST_FORWARD
(Som ett undantag körde jag också tester som utelämnade LOCAL
alternativ, och skillnaderna var försumbara.)
Som sagt, detta är inte nödvändigtvis sant för *alla* markörer. I det här fallet talar jag enbart om markörer där du bara läser data från markören, endast i riktning framåt, och du inte uppdaterar den underliggande data (varken med tangenten eller med WHERE CURRENT OF<) /kod> ). Det är tester för en annan dag.