Jeg har flere gange skrevet om at bruge markører, og hvordan det i de fleste tilfælde er mere effektivt at omskrive dine markører ved hjælp af sæt-baseret logik.
Jeg er dog realistisk.
Jeg ved, at der er tilfælde, hvor markører er "påkrævet" – du skal kalde en anden lagret procedure eller sende en e-mail for hver række, du udfører vedligeholdelsesopgaver mod hver database, eller du kører en enkeltstående opgave, der simpelthen er ikke værd at investere tid i at konvertere til sæt-baseret.
Hvordan du (sandsynligvis) gør det i dag
Uanset grunden til at du stadig bruger markører, skal du i det mindste passe på ikke at bruge de ret dyre standardindstillinger. De fleste mennesker starter deres markører sådan her:
DECLARE c CURSOR FOR SELECT whatever FROM ...
Nu igen, for ad-hoc, enkeltstående opgaver, er dette nok helt fint. Men der er...
Andre måder at gøre det på
Jeg ønskede at køre nogle test ved hjælp af standardindstillingerne og sammenligne dem med forskellige markørindstillinger såsom LOCAL
, STATISK
, READ_ONLY
og FAST_FORWARD
. (Der er et væld af muligheder, men det er dem, der oftest bruges, da de er anvendelige til de mest almindelige typer markøroperationer, som folk bruger). også indvirkningen på tempdb og hukommelse, både efter en kold servicegenstart og med en varm cache.
Forespørgslen, jeg besluttede at sende til markøren, er en meget enkel forespørgsel mod sys.objects
, i prøvedatabasen AdventureWorks2012. Dette returnerer 318.500 rækker på mit system (et meget ydmygt 2-core 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;
Så pakkede jeg denne forespørgsel ind i en markør med forskellige muligheder (inklusive standardindstillingerne) og kørte nogle tests, der målte Total Server Memory, sider allokeret til tempdb (ifølge sys.dm_db_task_space_usage
og/eller sys.dm_db_session_space_usage
), og samlet varighed. Jeg forsøgte også at observere tempdb-stridigheder ved hjælp af scripts fra Glenn Berry og Robert Davis, men på mit sølle system kunne jeg ikke opdage nogen som helst påstand. Jeg er selvfølgelig også på SSD, og absolut intet andet kører på systemet, så det kan være ting, du vil tilføje til dine egne tests, hvis tempdb er mere tilbøjelig til at være en flaskehals.
Så i sidste ende så forespørgslerne nogenlunde sådan her ud, med diagnostiske forespørgsler peppet ind på passende 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;
Resultater
Varighed
Helt uden tvivl er den vigtigste og mest almindelige foranstaltning, "hvor lang tid tog det?" Nå, det tog næsten fem gange så lang tid at køre en markør med standardindstillingerne (eller med kun LOCAL
specificeret), sammenlignet med at angive enten STATIC
eller FAST_FORWARD
:
Hukommelse
Jeg ønskede også at måle den ekstra hukommelse, som SQL Server ville anmode om, når hver markørtype blev opfyldt. Så jeg genstartede simpelthen før hver kold cache-test, og målte ydeevnetælleren Total Server Memory (KB)
før og efter hver test. Den bedste kombination her var LOCAL FAST_FORWARD
:
tempdb-brug
Dette resultat var overraskende for mig. Da definitionen af en statisk markør betyder, at den kopierer hele resultatet til tempdb, og det er faktisk udtrykt i sys.dm_exec_cursors
som SNAPSHOT
, Jeg forventede, at hit på tempdb-sider ville være højere med alle statiske varianter af markøren. Dette var ikke tilfældet; igen ser vi et omkring 5X hit på tempdb-brug med standardmarkøren og den med kun LOCAL
specificeret:
Konklusion
I årevis har jeg understreget, at følgende mulighed altid skal angives for dine markører:
LOCAL STATIC READ_ONLY FORWARD_ONLY
Fra dette tidspunkt, indtil jeg har mulighed for at teste yderligere permutationer eller finde tilfælde, hvor det ikke er den hurtigste mulighed, vil jeg anbefale følgende:
LOCAL FAST_FORWARD
(Som en side, kørte jeg også tests, hvor jeg udelod LOCAL
mulighed, og forskellene var ubetydelige.)
Når det er sagt, er dette ikke nødvendigvis sandt for *alle* markører. I dette tilfælde taler jeg udelukkende om markører, hvor du kun læser data fra markøren, kun i en fremadgående retning, og du ikke opdaterer de underliggende data (enten med tasten eller ved hjælp af WHERE CURRENT OF ). Det er tests for en anden dag.