TSQL 中的关闭和释放游标以及 Try-Catch

feO*_*O2x 3 t-sql sql-server exception

我正在 TSQL 中编写一个存储过程,它使用事务和游标。我想知道是否应该将CLOSEandDEALLOCATE放在 TRY 块中 - 这些语句会抛出异常吗?

我的代码结构如下:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;

DECLARE myCursor CURSOR LOCAL STATIC FORWARD_ONLY READ_ONLY FOR
SELECT -- Select statement left out for brevity's sake

BEGIN TRY
    OPEN myCursor
    FETCH NEXT FROM myCursor -- INTO statement left out for brevity's sake

    WHILE @@FETCHSTATUS = 0
    BEGIN
        -- Here I process each item in the cursor
        -- and then fetch the items for the next loop run
        FETCH NEXT FROM myCursor
    END;

    CLOSE myCursor;
    DEALLOCATE myCursor;
    COMMIT TRANSACTION;

END TRY
BEGIN CATCH
    CLOSE myCursor;
    DEALLOCATE myCursor;
    ROLLBACK TRANSACTION;
    THROW;

END CATCH
Run Code Online (Sandbox Code Playgroud)

我有以下具体问题:

  • 在这种特定情况下可以CLOSE抛出DEALLOCATE异常吗?如果是,这意味着我需要另一个 TRY-CATCH 块来安全地关闭游标,否则,TRY 块中发生的实际错误可能会丢失。我在官方文档中找不到任何相关信息,但这个答案表明它不会这样做。
  • 我应该删除ROLLBACK TRANSACTIONCATCH 块中的语句吗?抛出异常时事务是否自动回滚?
  • 有什么办法可以避免这段代码中出现重复的CLOSEand语句吗?DEALLOCATE

马丁·史密斯发表评论后更新:

根据这个SO答案,我可以声明一个游标变量,当该变量超出范围时,该变量会自动关闭并释放。我不能 100% 确定这是否真的发生:根据DEALLOCATE 文档DEALLOCATE被调用,但在CLOSE 文档中,没有任何关于自动行为的说明。

附加问题:

  • 使用变量真的会自动关闭并释放游标吗?我对此表示怀疑,因为根据文档,CLOSE 不会自动调用。除非像引用计数这样的事情是在内部完成的。add
  • 如果它真的有效,为什么要对只被引用一次的本地游标调用 CLOSE 呢?

Sol*_*zky 5

在这种特定情况下可以CLOSE抛出DEALLOCATE异常吗?

我不明白任何人在任何情况下都会抛出错误。他们只是释放资源。如果游标已关闭或释放(取决于预期的情况),则期望游标存在的下游操作当然可能会出错

我应该删除块ROLLBACK TRANSACTION中的语句CATCH吗?抛出异常时事务是否自动回滚?

不,不要删除ROLLBACK. 无论如何,您都需要它,尽管这通常包含在IF (@@TRANCOUNT > 0).

如果XACT_ABORTOFF(默认),那么交易可能仍然处于打开状态,因此您需要执行ROLLBACK. 如果XACT_ABORTON,则事务已经回滚,但事务也处于不可提交状态,此时您唯一能做的就是发出ROLLBACK. 因此,无论哪种方式,您都需要ROLLBACK.

有什么办法可以避免这段代码中出现重复的CLOSEand语句吗?DEALLOCATE

不,不是真的。但是,您可以只使用该DEALLOCATE语句,因为它会首先关闭游标。(见下面的例子)

使用变量真的会自动关闭并释放游标吗?

不确定该变量,但我确实知道这是没有必要的。您所需要的只是让光标处于状态LOCAL(您已经在这样做),它将在批处理结束时自动释放。(见下面的例子)

如果它真的有效,为什么要调用CLOSE只被引用一次的本地游标呢?

可能是因为大多数人不知道本地游标会在批处理结束时自动释放(我直到做了测试才可以回答这个问题),以及游标的默认范围(除非在数据库级别,我怀疑大多数人都会搞乱该设置,不知道它将如何影响内置存储过程)GLOBAL,所以他们确实需要重新分配它们。

以下测试显示了LOCALGLOBAL(大多数系统上的默认值)游标之间的差异,并且它DEALLOCATE本身就很好(假设您不需要再次重新运行游标查询):

DECLARE myLocalCursor CURSOR LOCAL STATIC FORWARD_ONLY READ_ONLY FOR
SELECT TOP (10) [object_id] FROM sys.objects;

SELECT 'DECLARE (local)' AS [Step], @@CURSOR_ROWS AS [Rows], *
FROM   sys.dm_exec_cursors(@@SPID);
-- 1 row
GO

SELECT 'NEXT BATCH (local)' AS [Step], @@CURSOR_ROWS AS [Rows], *
FROM   sys.dm_exec_cursors(@@SPID);
-- no rows

DECLARE myGlobalCursor CURSOR GLOBAL STATIC FORWARD_ONLY READ_ONLY FOR
SELECT TOP (10) [object_id] FROM sys.objects;

SELECT 'DECLARE (global)' AS [Step], @@CURSOR_ROWS AS [Rows], *
FROM   sys.dm_exec_cursors(@@SPID);
-- 1 row
GO

SELECT 'NEXT BATCH (global)' AS [Step], @@CURSOR_ROWS AS [Rows], *
FROM   sys.dm_exec_cursors(@@SPID);
-- 1 row (same cursor_id as previous SELECT)

DEALLOCATE myGlobalCursor;

SELECT 'DEALLOCATE (global)' AS [Step], @@CURSOR_ROWS AS [Rows], *
FROM   sys.dm_exec_cursors(@@SPID);
-- no rows
GO
Run Code Online (Sandbox Code Playgroud)

和:

-- open cursor then deallocate (do not close first):
DECLARE CursorToDeallocate CURSOR GLOBAL STATIC FORWARD_ONLY READ_ONLY FOR
SELECT TOP (10) [object_id] FROM sys.objects;

SELECT 'DECLARE (global2)' AS [Step], @@CURSOR_ROWS AS [Rows], *
FROM   sys.dm_exec_cursors(@@SPID);
-- 1 row

OPEN CursorToDeallocate;
SELECT 'OPEN (global2)' AS [Step], @@CURSOR_ROWS AS [Rows], *
FROM   sys.dm_exec_cursors(@@SPID);
-- 1 row

DEALLOCATE CursorToDeallocate;

SELECT 'DEALLOCATE (global2)' AS [Step], @@CURSOR_ROWS AS [Rows], *
FROM   sys.dm_exec_cursors(@@SPID);
-- no rows
GO
Run Code Online (Sandbox Code Playgroud)

从您的文档问题来看:

CLOSE到时候还需要打电话吗DEALLOCATE?是否有一个自动机制可以做到这一点?

一般来说,是的,你CLOSE首先保持光标分配,然后你就可以了DEALLOCATE。如果您DEALLOCATE先调用,则光标会自动关闭。通过仅关闭游标,它仍然被定义,并且可以通过再次执行它来重新运行OPEN(不需要重新声明它)。

关于自动CLOSEDEALLOCATE

  1. LOCAL批处理结束时游标会自动关闭并释放
  2. 请参阅SET CURSOR_CLOSE_ON_COMMIT

DEALLOCATE您能提供内部实际执行情况的信息吗?

据我所知,只需从(好吧,无论数据存储在何处)DEALLOCATE删除游标定义。sys.dm_exec_cursors当然,如果游标尚未关闭,则将其关闭(至少在某种意义上,没有游标意味着没有游标可以打开或关闭,或者维护任何锁或任何资源;公平地说,这仅仅描述了最终结果,而不是代码到达那里的路径,所以我不能从技术上说是否DEALLOCATE真的调用 CLOSE或只是重复由 处理的某些功能CLOSE,我也不确定这种区别是否对任何人以外的人来说很重要维护该代码)。

我做了一些测试,发现两者DEALLOCATE本身CLOSE具有相同的效果:表上基于“CURSOR”的锁被删除。我没有发现任何证据表明DEALLOCATE其本身留下了任何锁。我SCROLL_LOCKS在执行UPDATE ... WHERE CURRENT OF Cursor. 每个都在页面上锁定,FETCH在键上锁定,在表上锁定,所有锁定所有者类型均为。它本身复制了这 3 个锁作为所有者类型(嗯,页面锁和密钥锁类型存在细微差别)。在or后,仅保留事务锁。IUUIXCURSORUPDATETRANSACTIONDEALLOCATECLOSE