T-SQL:在更新触发器中 CLOSE/DEALLOCATE 游标的正确方法
假设我有一个这样的触发器:
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 块吗?
Let's say I've got a trigger like this:
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
I want to be sure that Cursor1 is always closed and deallocated. Even myProc1 or myProc2 fails.
Shall I use try/catch block?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您可以使用 CURSOR_STATUS() 函数。
参考:http://msdn.microsoft.com/ en-us/library/ms177609.aspx
You could use the CURSOR_STATUS() function.
reference: http://msdn.microsoft.com/en-us/library/ms177609.aspx
是的,使用 TRY/CATCH 但请确保在之后取消分配等。
不幸的是,SQL Server 中没有finally。
但是,我建议将其包装在另一个 try/catch 中,
触发器中的游标是否是一个好主意是另一回事......
Yes, use TRY/CATCH but make sure you deallocate etc after.
Unfortunately, there is no finally in SQL Server.
However, I suggest wrapping this in another try/catch
Whether a cursor in a trigger is a good idea is a different matter...
十年后,我认为我应该为这个特定问题添加一些信息。
您的问题有两个主要解决方案。首先,使用
LOCAL
游标声明:这将您的特定游标仅限于活动会话,而不是服务器的全局上下文,假设没有其他操作调用此游标。原则上类似的是使用游标变量,它看起来像这样:
在使用游标变量时,除了将范围管理在范围内之外,您还可以随时使用
SET
语法覆盖它您的特定会话就像前面的示例一样。通过覆盖游标上下文,您可以有效地释放它拥有的任何过去的引用。也就是说,这两种方法都通过将光标的状态链接到当前连接的活动来实现您的初衷。如果您的应用程序上下文使用连接池,这可能会留下延迟锁,在这种情况下,您应该使用
Try-Catch
模式,如下所示:以这种方式使用该模式可以减少代码重复,或者需要检查光标的状态。基本上,游标初始化应该是安全的,就像 open 语句一样。一旦游标打开,您将希望始终从会话中关闭它并取消分配它,并且假设游标已打开,这应该始终是一个安全的操作(我们刚刚建立的应该始终是一个安全的操作)。因此,将它们留在
Try-Catch
范围之外意味着所有内容都可以在Catch
块之后的最后整齐地关闭。值得一提的是,我指定了游标的 READ_ONLY 属性以及 FORWARD_ONLY 属性,因为您的示例代码没有在集合中的记录之间来回滚动。如果您要修改这些过程中的基础行,那么最好使用 STATIC 游标,以确保不会意外导致无限循环。这应该不是问题,因为您使用
INSERTED
表来管理光标上下文,但对于其他潜在用例仍然值得一提。如果您想了解有关 SQL Server 中游标的更多信息,我强烈建议您阅读 这篇关于该主题的博客文章,他详细解释了游标的各种修饰符是什么,以及它们在数据库引擎中的效果。
Ten years later, I figure I should add some information to this particular question.
There are two primary solutions to your problem. First, use a
LOCAL
cursor declaration:This limits your particular cursor to only your active session, rather than the global context of the server, assuming no other action is calling into this cursor. Similar in principle is to use a Cursor Variable, which would look like this:
In using a cursor variable, you can always overwrite it at anytime using the
SET
syntax, in addition to managing the scope to being within your particular session like the previous example. By overwriting the cursor context, you effectively deallocate any past reference it had. That said, both of these approaches accomplish your original intention by linking the status of the cursor to the activity of the current connection.This might leave a lingering lock if your app context is using Connection Pooling, in which case you should use the
Try-Catch
pattern as follows:Using the pattern in this way reduces the code duplication, or need to check the status of the cursor. Basically, the Cursor-initialization should be safe, as is the open statement. Once the cursor is open, you will want to always close-deallocate it from the session, and that should always be a safe action assuming the cursor has been opened (which we just established should always be a safe operation). As such, leaving those outside the confines of the
Try-Catch
means that everything can be neatly closed at the end, after theCatch
block.It's worth mentioning that I specified the
READ_ONLY
attribute of the cursor, as well asFORWARD_ONLY
, since your sample code didn't scroll back-and-forth between records in the set. If you are modifying the underlying rows in those procedures, you are probably better off using aSTATIC
cursor to ensure you don't accidentally cause an infinite loop. That shouldn't be a problem since you're using theINSERTED
table to manage your cursor context, but still worth mentioning for other potential use cases.If you want to learn more about cursors in SQL Server, I highly recommend reading this blog post on the subject, as he goes into great detail explaining what the various modifiers of a cursor are, and the effects they have within the Database Engine.
您应该做的是永远不要在触发器中使用游标。而是编写正确的基于集合的代码。如果有人将数据导入到包含 100,000 条新记录的表中,您将锁定该表数小时,并使数据库彻底停止运行。在触发器中使用游标是一种非常糟糕的做法。
What you should do is never ever use a cursor in a trigger. Write correct set-based code instead. If someone did an import of data into your table of 100,000 new records you would lock up the table for hours and bring your database to a screaming halt. It is a very poor practice to use a cursor in a trigger.