Několikrát jsem psal o používání kurzorů a o tom, jak je ve většině případů efektivnější přepisovat kurzory pomocí logiky založené na množinách.
Jsem však realista.
Vím, že existují případy, kdy jsou kurzory „vyžadovány“ – potřebujete zavolat jinou uloženou proceduru nebo poslat e-mail pro každý řádek, provádíte úlohy údržby pro každou databázi nebo spouštíte jednorázovou úlohu, která jednoduše nestojí za to investovat čas do převodu na sadu.
Jak to dnes (pravděpodobně) děláte
Bez ohledu na důvod, proč stále používáte kurzory, měli byste být přinejmenším opatrní, abyste nepoužívali poměrně drahé výchozí možnosti. Většina lidí začíná své kurzory takto:
DECLARE c CURSOR FOR SELECT whatever FROM ...
Nyní znovu, pro ad-hoc, jednorázové úkoly, je to pravděpodobně v pořádku. Ale existují…
Další způsoby, jak to udělat
Chtěl jsem spustit některé testy pomocí výchozích hodnot a porovnat je s různými možnostmi kurzoru, jako je LOCAL
, STATIC
, READ_ONLY
a FAST_FORWARD
. (Existuje spousta možností, ale tyto jsou nejčastěji používané, protože jsou použitelné pro nejběžnější typy kurzorových operací, které lidé používají.) Nejen, že jsem chtěl otestovat hrubou rychlost několika různých kombinací, ale také dopad na tempdb a paměť, a to jak po studeném restartu služby, tak s teplou mezipamětí.
Dotaz, který jsem se rozhodl zadat kurzoru, je velmi jednoduchý dotaz na sys.objects
, ve vzorové databázi AdventureWorks2012. To vrátí 318 500 řádků v mém systému (velmi skromný 2jádrový systém se 4 GB RAM):
SELECT c1.[object_id] FROM sys.objects AS c1 CROSS JOIN (SELECT TOP 500 name FROM sys.objects) AS c2;
Poté jsem tento dotaz zabalil do kurzoru s různými možnostmi (včetně výchozích hodnot) a provedl jsem několik testů, měření celkové paměti serveru, stránek přidělených tempdb (podle sys.dm_db_task_space_usage
a/nebo sys.dm_db_session_space_usage
) a celkovou dobu trvání. Zkoušel jsem také pozorovat spory tempdb pomocí skriptů od Glenna Berryho a Roberta Davise, ale na svém mizerném systému jsem nemohl detekovat žádný spor. Samozřejmě jsem také na SSD a v systému neběží absolutně nic jiného, takže toto mohou být věci, které budete chtít přidat do svých vlastních testů, pokud je pravděpodobnější, že tempdb bude překážkou.
Takže nakonec dotazy vypadaly asi takto, s diagnostickými dotazy zakomponovanými na příslušných místech:
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;
Výsledky
Trvání
Zcela pravděpodobně nejdůležitější a nejběžnější měřítko je, "jak dlouho to trvalo?" Spuštění kurzoru s výchozími možnostmi (nebo pouze s LOCAL
) trvalo téměř pětkrát déle specifikováno), ve srovnání se zadáním buď STATIC
nebo FAST_FORWARD
:
Paměť
Chtěl jsem také změřit další paměť, kterou by SQL Server požadoval při splnění každého typu kurzoru. Takže jsem před každým testem studené mezipaměti jednoduše restartoval a změřil počítadlo výkonu Total Server Memory (KB)
před a po každém testu. Nejlepší kombinace zde byla LOCAL FAST_FORWARD
:
Využití tempdb
Tento výsledek mě překvapil. Protože definice statického kurzoru znamená, že zkopíruje celý výsledek do databáze tempdb a ve skutečnosti je vyjádřen v sys.dm_exec_cursors
jako SNAPSHOT
, očekával jsem, že zásah na stránkách tempdb bude vyšší se všemi statickými variantami kurzoru. Nebylo tomu tak; opět vidíme zhruba 5násobný zásah při použití databáze tempdb s výchozím kurzorem a kurzorem pouze s LOCAL
specifikováno:
Závěr
Po léta zdůrazňuji, že pro vaše kurzory by měla být vždy uvedena následující možnost:
LOCAL STATIC READ_ONLY FORWARD_ONLY
Od tohoto okamžiku, dokud nebudu mít možnost otestovat další permutace nebo najít případy, kdy to není nejrychlejší možnost, budu doporučovat následující:
LOCAL FAST_FORWARD
(Mimochodem jsem také provedl testy s vynecháním LOCAL
a rozdíly byly zanedbatelné.)
To však nemusí nutně platit pro *všechny* kurzory. V tomto případě mluvím pouze o kurzorech, kde pouze čtete data z kurzoru, pouze ve směru dopředu a neaktualizujete podkladová data (buď pomocí klíče nebo pomocí WHERE CURRENT OF
). To jsou testy na další den.