假设我有一个这样的触发器:
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块吗?
答案 0 :(得分:38)
您可以使用CURSOR_STATUS()函数。
if CURSOR_STATUS('global','cursor_name') >= 0
begin
close cursor_name
deallocate cursor_name
end
答案 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,因为他会详细介绍游标的各种修饰符以及它们在其中的作用数据库引擎。