T-SQL:在更新触发器中CLOSE / DEALLOCATE游标的正确方法

时间:2009-09-11 09:33:57

标签: database sql-server-2005 tsql cursor

假设我有一个这样的触发器:

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 个答案:

答案 0 :(得分:38)

您可以使用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

答案 1 :(得分:15)

是的,请使用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

触发器中的光标是否是个好主意是另一回事......

答案 2 :(得分:1)

你应该做的是永远不要在触发器中使用光标。编写正确的基于集合的代码。如果有人将数据导入到包含100,000条新记录的表中,您可能会将表锁定数小时并使数据库停止尖叫。在触发器中使用光标是一种非常糟糕的做法。

答案 3 :(得分:0)

十年后,我想我应该为这个特定问题添加一些信息。

您的问题有两种主要解决方案。首先,使用LOCAL游标声明:

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

这将您的特定游标限制为仅活动会话,而不是服务器的全局上下文,前提是没有其他操作正在调用此游标。原则上类似的是使用游标变量,它看起来像这样:

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;

以这种方式使用模式可以减少代码重复,或者需要检查游标的状态。基本上,Cursor初始化和open语句一样应该安全。游标打开后,您将始终希望从会话中关闭它的分配,并且假设游标已打开(这是我们刚建立的应该始终是安全的操作),这应该始终是安全的操作。因此,将那些内容排除在Try-Catch的范围之外意味着在Catch块之后,所有内容都可以在末尾整齐地关闭。

值得一提的是,我指定了游标的READ_ONLY属性以及FORWARD_ONLY,因为示例代码没有在记录集中的记录之间来回滚动。如果要在这些过程中修改基础行,则最好使用STATIC游标,以确保不会意外导致无限循环。这不是问题,因为您正在使用INSERTED表来管理光标上下文,但是对于其他潜在用例仍然值得一提。

如果您想了解有关SQL Server中游标的更多信息,强烈建议阅读this blog post,因为他会详细介绍游标的各种修饰符以及它们在其中的作用数据库引擎。