T-SQL: правильный способ ЗАКРЫТЬ / ОТКЛЮЧИТЬ курсор в триггере обновления - PullRequest
17 голосов
/ 11 сентября 2009

Допустим, у меня есть триггер, подобный этому:

CREATE TRIGGER trigger1
   ON [dbo].[table1] 
   AFTER UPDATE
AS 
BEGIN               
    --declare some vars
    DECLARE @Col1 SMALLINT 
    DECLARE @Col1 TINYINT 

    --declare cursor        
    DECLARE Cursor1 CURSOR FOR 
    SELECT Col1, Col2 FROM INSERTED             

    --do the job
    OPEN Cursor1
    FETCH NEXT FROM Cursor1 INTO @Col1, @Col2

    WHILE @@FETCH_STATUS = 0
    BEGIN
        IF ...something...
        BEGIN           
            EXEC myProc1 @param1 = @Col1, @Param2 = @Col2
        END             
        ELSE
        IF ...something else...
        BEGIN           
            EXEC myProc2 @param1 = @Col1, @Param2 = @Col2
        END     

        FETCH NEXT FROM Cursor1 INTO @Col1, @Col2               
    END

    --clean it up       
    CLOSE Cursor1
    DEALLOCATE Cursor1                  
END

Я хочу быть уверен, что Cursor1 всегда закрыт и освобожден. Даже myProc1 или myProc2 дает сбой.

Должен ли я использовать блок try / catch?

Ответы [ 4 ]

39 голосов
/ 20 января 2010

Вы можете использовать функцию CURSOR_STATUS ().

if CURSOR_STATUS('global','cursor_name') >= 0 
begin
 close cursor_name
  deallocate cursor_name 
end

ссылка : http://msdn.microsoft.com/en-us/library/ms177609.aspx

15 голосов
/ 15 сентября 2009

Да, используйте TRY / CATCH, но убедитесь, что вы освободили и т.д. после. К сожалению, в SQL Server нет окончательно.

Однако я предлагаю обернуть это в другой try / catch

CREATE TRIGGER trigger1 ON [dbo].[table1] AFTER UPDATE
AS 
BEGIN                           
    --declare some vars
    DECLARE @Col1 SMALLINT, @Col1 TINYINT 

    BEGIN TRY
        --declare cursor            
        DECLARE Cursor1 CURSOR FOR 
        SELECT Col1, Col2 FROM INSERTED                     

        --do the job
        OPEN Cursor1
        FETCH NEXT FROM Cursor1 INTO @Col1, @Col2

        WHILE @@FETCH_STATUS = 0
        BEGIN
            IF ...something...
                    EXEC myProc1 @param1 = @Col1, @Param2 = @Col2
            ELSE
            IF ...something else...
                    EXEC myProc2 @param1 = @Col1, @Param2 = @Col2

            FETCH NEXT FROM Cursor1 INTO @Col1, @Col2                               
        END
    END TRY
    BEGIN CATCH
        --do what you have to
    END CATCH

    BEGIN TRY
        --clean it up               
        CLOSE Cursor1
        DEALLOCATE Cursor1                                  
    END TRY
    BEGIN CATCH
        --do nothing
    END CATCH
END

Является ли курсор в триггере хорошей идеей, это другой вопрос ...

1 голос
/ 11 сентября 2009

То, что вы должны сделать, никогда не использовать курсор в триггере. Вместо этого напишите правильный код на основе набора. Если кто-то сделает импорт данных в вашу таблицу из 100 000 новых записей, вы заблокируете таблицу на несколько часов и закроете свою базу данных. Использование курсора в триггере очень плохая практика.

0 голосов
/ 24 июня 2019

Десять лет спустя я полагаю, что должен добавить некоторую информацию к этому конкретному вопросу.

Есть два основных решения вашей проблемы. Сначала используйте объявление курсора LOCAL:

DECLARE --Operation
    Cursor1 -- Name
CURSOR -- Type
    LOCAL READ_ONLY FORWARD_ONLY -- Modifiers
FOR -- Specify Iterations
SELECT Col1, Col2 FROM INSERTED;

Это ограничивает ваш конкретный курсор только вашим активным сеансом, а не глобальным контекстом сервера, при условии, что никакое другое действие не вызывает этот курсор. Аналогично в принципе использовать переменную Cursor, которая будет выглядеть следующим образом:

DECLARE @Cursor1 CURSOR;
SET @Cursor1 = CURSOR LOCAL READ_ONLY FORWARD_ONLY FOR SELECT Col1, Col2 FROM INSERTED;

Используя переменную курсора, вы всегда можете перезаписать ее в любое время, используя синтаксис SET, в дополнение к управлению областью, чтобы она находилась в вашей конкретной сессии, как в предыдущем примере. Перезаписывая контекст курсора, вы фактически освобождаете любую прошлую ссылку, которая у него была. Тем не менее, оба эти подхода реализуют ваше первоначальное намерение, связывая состояние курсора с активностью текущего соединения.

Это может оставить длительную блокировку, если контекст вашего приложения использует пул соединений, и в этом случае вы должны использовать шаблон Try-Catch следующим образом:

CREATE TRIGGER trigger1
   ON [dbo].[table1] 
   AFTER UPDATE
AS 
BEGIN               
    --declare some vars
    DECLARE @Col1 SMALLINT;
    DECLARE @Col2 TINYINT;

    --declare cursor        
    DECLARE 
        Cursor1 
    CURSOR 
        LOCAL READ_ONLY FORWARD_ONLY 
    FOR 
        SELECT 
            Col1, 
            Col2 
        FROM 
            INSERTED;

    --do the job
    OPEN Cursor1;

    BEGIN TRY

        FETCH 
            NEXT 
        FROM 
            Cursor1 
        INTO 
            @Col1, 
            @Col2;

        WHILE @@FETCH_STATUS = 0
            BEGIN
                IF -- my condition
                    EXEC myProc1 @param1 = @Col1, @Param2 = @Col2;
                ELSE IF -- additional condition
                    EXEC myProc2 @param1 = @Col1, @Param2 = @Col2;

                FETCH 
                    NEXT 
                FROM 
                    Cursor1 
                INTO 
                    @Col1, 
                    @Col2;
            END;
    END TRY

    BEGIN CATCH
        -- Error Handling
    END CATCH

    --clean it up       
    CLOSE Cursor1;
    DEALLOCATE Cursor1;
END;

Использование шаблона таким образом уменьшает дублирование кода или необходимость проверки состояния курсора. По сути, инициализация курсора должна быть безопасной, как и оператор open. Как только курсор открыт, вы захотите всегда закрывать-освобождать его из сеанса, и это всегда должно быть безопасным действием при условии, что курсор был открыт (который мы только что установили, всегда должен быть безопасной операцией). Таким образом, выход за пределы Try-Catch означает, что все может быть аккуратно закрыто в конце, после блока Catch.

Стоит отметить, что я указал атрибут READ_ONLY для курсора, а также FORWARD_ONLY, поскольку ваш пример кода не прокручивался назад и вперед между записями в наборе. Если вы изменяете базовые строки в этих процедурах, вам, вероятно, лучше использовать курсор STATIC, чтобы случайно не вызвать бесконечный цикл. Это не должно быть проблемой, поскольку вы используете таблицу INSERTED для управления контекстом курсора, но все же стоит упомянуть о других возможных случаях использования.

Если вы хотите больше узнать о курсорах в SQL Server, я настоятельно рекомендую прочитать эту запись в блоге на эту тему, поскольку он очень подробно объясняет, каковы различные модификаторы курсора, и как эффекты, которые они имеют в Database Engine.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...