数据库
首页 > 数据库> > 当用户取消查询时,如何回滚SQL CLR存储过程?

当用户取消查询时,如何回滚SQL CLR存储过程?

作者:互联网

我正在尝试创建一个SQL CLR存储过程,该过程将创建一个表,将表名传递到服务上,该服务将向其中批量插入一些数据,显示表的结果,然后清理表.

到目前为止,我已经尝试过:

>使用SqlTransaction.取消交易是可行的,但是它使我的查询窗口处于无法继续处理的状态.

The transaction active in this session has been committed or aborted by another session

>使用TransactionScope.与1相同.
>通过发出DROP TABLE SqlCommand手动清理finally子句中的表.虽然我的SqlContext.Pipe.Send()在发出命令之前确实可以运行,但这似乎没有运行.它似乎与任何时间限制都没有关系,因为如果我在打印另一行之前发出Thread.Sleep(2000),它仍然会打印第二行,而命令.ExecuteNonQuery()将在打印第二行之前停止.
>将手动清除代码放入CER或SafeHandle中.这是行不通的,因为拥有CER需要某些保证,包括不分配额外的内存或调用未使用ReliabilityContract装饰的方法.

我在这里错过明显的东西吗?如何处理用户取消其查询?

编辑:每种情况下代码都有多次迭代,但是所采取的一般措施如下:

    [SqlProcedure]
    public static void GetData(SqlString code)
    {
        Guid guid = Guid.NewGuid();
        using (var connection = new SqlConnection("context connection=true"))
        {
            connection.Open();

            try
            {
                SqlContext.Pipe?.Send("Constrain");

                SqlCommand command1 = new SqlCommand($"CREATE TABLE qb.{code}_{guid:N} (Id INT)", connection);
                command1.ExecuteNonQuery();
                SqlContext.Pipe?.Send($"Create: qb.{code}_{guid:N}");

                //emulate service call
                Thread.Sleep(TimeSpan.FromSeconds(10));

                SqlContext.Pipe?.Send($"Done: qb.{code}_{guid:N}");
            }
            finally
            {
                SqlContext.Pipe?.Send("1");
                //drop table here instead of sleep
                Thread.Sleep(2000);
                SqlContext.Pipe?.Send("2");
            }
        }
    }

解决方法:

不幸的是,SQLCLR不能很好地处理查询取消.但是,给出错误消息后,这似乎意味着取消操作会执行自己的ROLLBACK.您是否尝试过不在SQLCLR代码中使用事务,而是从外部处理它?如:

>开始交易;
> EXEC SQLCLR_Stored_Procedure;
>如果(@@ TRANCOUNT> 0)ROLLBACK TRAN;

上面提到的工作流程将需要执行.通过创建一个包装器T-SQL存储过程来执行这3个步骤,并且仅对包装器存储过程赋予EXECUTE权限,可以很容易地做到这一点.如果随后需要SQLCLR存储过程的权限,则可以使用模块签名轻松完成此操作:

>在与SQLCLR存储过程相同的数据库中创建非对称密钥
>从该非对称密钥创建用户
>授予对SQLCLR存储过程的基于密钥的用户EXECUTE权限
>使用该非对称密钥使用ADD SIGNATURE对包装器T-SQL存储过程进行签名

这4个步骤允许T-SQL包装器proc执行SQLCLR proc,而实际的应用程序Login仅可以执行T-SQL包装器proc :-).并且,如果取消在执行ROLLBACK之前中止执行,则在连接关闭时应该自动回滚Transaction.

另外,您是否将XACT_ABORT设置为ON或OFF?更新:O.P.声明将其设置为OFF,并且将其设置为ON似乎没有任何不同.

您是否尝试过检查finally块中的连接状态?我很确定取消时SqlConnection是关闭的.您可以在finally块中尝试以下方法:

>测试连接状态,如果已关闭,请重新打开SqlConnection,然后执行非查询命令.

更新:O.P.指出连接仍处于打开状态.好吧,关闭它然后重新打开呢?

更新2:O.P.经过测试,发现无法重新打开连接.
>由于上下文仍然可用,如您的打印命令所能证明的那样,请使用SqlContext.Pipe.ExecuteAndSend(new SqlCommand(“ DROP TABLE ..;”))之类的东西.

更新:O.P.指出这不起作用.

或者,由于您在代码中创建了一个保证唯一的表名,因此您可以尝试将该表创建为全局临时表(即以两个井号开头的## TableName),该表将a)可用于批量导入过程, b)当连接完全关闭时自行清理.使用这种方法,从技术上讲,您无需执行任何手动清理.

当然,启用连接池后,仅在重新打开连接并执行第一个命令后,才会进行自动清除.为了强制立即清除,您必须在禁用连接池的情况下连接到SQL Server.仅当要执行包含Pooling = false;的此存储过程时,才可能使用其他连接字符串吗?考虑到如何使用此存储过程,似乎仅通过对此一个特定的调用禁用连接池就不会导致任何明显的性能下降.为了更好地理解启用或禁用连接池如何影响临时对象的自动清除,请参阅我刚刚发表的博客文章,其中详细介绍了此行为:

Sessions, Temporary Objects, and the Afterlife

这种方法可能是最好的方法,因为您可能无法保证将执行ROLLBACK(提到了第一种方法)或将执行finally子句(假设您曾经使用过该方法).最后,未提交的事务将被回滚,但是如果有人通过SSMS执行此操作,并且在没有ROLLBACK的情况下中止,则它们仍处于打开的事务中并且可能不知道它.另外,考虑一下强制关闭连接,终止会话或关闭/重新启动服务器.在这些情况下,tempdb是您的朋友,无论是使用全局临时表,还是至少在tempdb中创建永久表,以便在下次SQL Server启动时将其自动删除(由于tempdb是新创建的,如在每次启动SQL Server服务时复制模型).

标签:sqlclr,c,sql-server
来源: https://codeday.me/bug/20191110/2014490.html