Няколко пъти съм писал за използването на курсори и как в повечето случаи е по-ефективно да пренапишете курсорите си с помощта на логика, базирана на набор.
Все пак съм реалист
Знам, че има случаи, в които курсорите са „задължителни“ – трябва да извикате друга съхранена процедура или да изпратите имейл за всеки ред, изпълнявате задачи за поддръжка на всяка база данни или изпълнявате еднократна задача, която просто не си струва да инвестирате време за преобразуване в базирано на набор.
Как (вероятно) го правите днес
Независимо от причината, поради която все още използвате курсори, трябва най-малкото да внимавате да не използвате доста скъпите опции по подразбиране. Повечето хора започват курсорите си така:
DECLARE c CURSOR FOR SELECT whatever FROM ...
Сега отново, за ad-hoc, еднократни задачи, това вероятно е добре. Но има...
Други начини да го направите
Исках да изпълня някои тестове, използвайки настройките по подразбиране и да ги сравня с различни опции на курсора, като LOCAL
, STATIC
, САМО ЗА ЧЕТЕНЕ
и FAST_FORWARD
. (Има много опции, но това са най-често използваните, тъй като са приложими за най-често срещаните видове операции с курсор, които хората използват.) Не само исках да тествам суровата скорост на няколко различни комбинации, но също и въздействието върху tempdb и паметта, както след рестартиране на студена услуга, така и при топъл кеш.
Заявката, която реших да подавам към курсора, е много проста заявка към sys.objects
, в примерната база данни на AdventureWorks2012. Това връща 318 500 реда в моята система (много скромна 2-ядрена система с 4 GB RAM):
SELECT c1.[object_id] FROM sys.objects AS c1 CROSS JOIN (SELECT TOP 500 name FROM sys.objects) AS c2;
След това увих тази заявка в курсор с различни опции (включително настройките по подразбиране) и проведох някои тестове, измервайки общата сървърна памет, страниците, разпределени към tempdb (според sys.dm_db_task_space_usage
и/или sys.dm_db_session_space_usage
) и обща продължителност. Също така се опитах да наблюдавам tempdb конкуренцията, използвайки скриптове от Глен Бери и Робърт Дейвис, но в моята нищожна система не можах да открия каквото и да е спор. Разбира се, аз също съм на SSD и абсолютно нищо друго не работи в системата, така че това може да са неща, които искате да добавите към собствените си тестове, ако tempdb е по-вероятно да бъде пречка.
Така че в крайна сметка заявките изглеждаха така, с диагностични заявки, включени в подходящи точки:
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;
Резултати
Продължителност
Може би най-важната и често срещана мярка е "колко време отне?" Е, отне почти пет пъти повече време за стартиране на курсора с опциите по подразбиране (или само с LOCAL
посочен), в сравнение с уточняването на STATIC
или FAST_FORWARD
:
Памет
Исках също да измеря допълнителната памет, която SQL Server ще поиска при изпълнение на всеки тип курсор. Затова просто рестартирах преди всеки тест за студен кеш, като измервах брояча на производителността Обща памет на сървъра (KB)
преди и след всеки тест. Най-добрата комбинация тук беше LOCAL FAST_FORWARD
:
използване на tempdb
Този резултат беше изненадващ за мен. Тъй като дефиницията на статичен курсор означава, че той копира целия резултат в tempdb и всъщност се изразява в sys.dm_exec_cursors
като СНИМКА
, очаквах хитът на tempdb страници да бъде по-висок с всички статични варианти на курсора. Това не беше така; отново виждаме приблизително 5X удар при използване на tempdb с курсора по подразбиране и този само с LOCAL
посочено:
Заключение
От години наблягах, че следната опция винаги трябва да се посочва за вашите курсори:
LOCAL STATIC READ_ONLY FORWARD_ONLY
От този момент нататък, докато имам възможност да тествам допълнителни пермутации или да намеря случаи, когато това не е най-бързият вариант, ще препоръчвам следното:
LOCAL FAST_FORWARD
(Като настрана, аз също проведох тестове, пропускайки LOCAL
опция и разликите бяха незначителни.)
Въпреки това, това не е непременно вярно за *всички* курсори. В този случай говоря единствено за курсори, при които четете данни само от курсора, само в посока напред и не актуализирате основните данни (или чрез ключа, или с помощта на WHERE CURRENT OFкод> ). Това са тестове за друг ден.