【问题标题】:How to wait for a SQL Server database rollback to complete?如何等待 SQL Server 数据库回滚完成?
【发布时间】:2017-03-31 12:17:37
【问题描述】:

使用 ADO.NET SqlClient 类时,有几种方法可以回滚事务。通过显式调用SqlTransaction.Rollback,或具有命令超时,或事务范围内的事务超时等。

但是,在使用 SQL Server(2012 或更高版本)时,如何检测此回滚何时完成?对于长时间运行的事务,回滚可能需要很长时间,并且通常在数据库上相当 IO 密集,因此立即重试事务可能不明智。在我们的情况下,我们希望等待回滚完成。

(在我们的特定情况下,由于使用 TransactionScope 类的事务超时而发生回滚,但我更喜欢适用于任何类型回滚的方法)。

我看过sys.dm_exec_requests,起初看起来很有希望。它最初的命令类型为KILL/ROLLBACK,并报告了进度百分比。 (如果我从原始事务中捕获 SPID,我将能够轮询该表并等待它完成)。然而,大约在进行到一半时,我注意到它改为AWAITING COMMAND(而回滚仍在执行中)。

我还看了KILL <SPID> WITH STATUSONLY。如果没有正在进行的回滚,这似乎会产生错误,因此它也可以用于轮询方法,但我注意到如果我从 SSMS 运行 BEGIN TRAN ... ROLLBACK TRAN 批处理,则会报告一个错误,指出没有回滚回滚时的进度。在这种情况下,dm_exec_requests 表将 ROLLBACK TRANSACTION 报告为命令类型。

那么如何才能以可靠的方式等待回滚完成呢?

【问题讨论】:

  • 因为在这种情况下,回滚期间 SQL 服务器上的负载太高,甚至无法尝试重新运行事务。所以我们希望等到回滚完成并且数据库服务器有更多资源可用于执行下一个实际事务。
  • 抱歉,我删除了我的评论,因为我在我的问题中找到了答案。我在问“如果您在 READ_COMMITED 中,您的代码不应读取未提交的数据,为什么还要打扰?”同样在 .NET 中,您的请求将在此选项中等待您的事务完成。
  • 类似的问题,虽然措辞不同。您似乎想等到当前事务提交或回滚后再尝试另一个事务。 stackoverflow.com/questions/18457955/…
  • @WilliamXifaras 这个问题似乎主要涉及事务之间的锁定。如果我的交易因任何原因中止,例如。 SqlCommand.ExecuteNonQuery事务超时10分钟后抛出异常,我想等待数据库完成回滚。
  • 如果是时间/性能问题,也许你应该把你的工作分解成更小的单元。就查询状态而言,sys.dm_exec_requests 似乎是您最好的选择。

标签: .net sql-server transactions


【解决方案1】:

您可以查看基于spidsp_who 的交易活动。 status 列将告诉您当前的事务状态并从 rollback 更改或在完成时消失。

如果您想获得有关 .NET 的反馈,请使用 SqlChangeMonitor(example) 缓存您的结果并等待您的 SqlChangeMonitor 通知来自 rollback 的更改。


如果您解决了超时问题,您可以查看:

使用 IsolationLevel.ReadCommitted。这样,您的 .NET 代码将等待您的 Commit 或 Rollback 完成。

(它看起来像 cmd.Connection.BeginTransaction(IsolationLevel.ReadCommitted)cmd 类型为 SqlCommand

【讨论】:

  • 我不相信它会。如果发生事务超时(例如在 TransactionScope 中),则会引发“事务中止”的异常,并且 .NET 代码的执行照常继续,而 SQL Server 可能会花费很长时间运行回滚。不知道为什么隔离级别会改变这一点?
  • 首先,您的请求不会超时。如果确实如此,则可能意味着两件事。您的请求设计不佳(通常是由于没有适当分段的高复杂性)或者您没有设置正确的CommandTimeout(默认设置为 30 秒)。在第二种情况下,使用前面的示例,您可以执行类似 cmd.CommandTimeout = 180 的操作(设置为 3 分钟)
  • 是的,希望请求不要超时。但是问题涉及它确实的情况,并且隐式启动了回滚。
  • 已编辑以匹配您的问题。但我仍然认为超时是不惜一切代价避免的。它充其量只会为您的项目添加难以维护/可读的代码。在最坏的情况下,它会增加意想不到的行为。
【解决方案2】:
SqlRollback.cs 

using System;
using System.Data.SqlClient;
using System.Threading;

namespace Events
{
    public class SqlRollback
    {
        private string _connString = "Server=(localdb)\\mssqllocaldb;Database=QueryIt.EmployeeDb;Trusted_Connection=true";
        public event EventHandler TransactionRolledBack;

        public void Transact()
        {
            using (SqlConnection conn = new SqlConnection(_connString))
            {
                conn.Open();

                var command = conn.CreateCommand();
                SqlTransaction transaction;
                transaction = conn.BeginTransaction("Transaction");

                command.Connection = conn;
                command.Transaction = transaction;

                try
                {
                    transaction.Save("checkpoint");
                    command.CommandText = "INSERT INTO Employees (name, discriminator) VALUES ('Dimitar', 'Bastun')";
                    command.ExecuteNonQuery();
                    throw new Exception();

                } catch (Exception ex)
                {
                    transaction.Rollback("checkpoint"); //Rolling back to the checkpoint
                    Thread.Sleep(5000); //Simulating some time
                    transaction.Commit();
                    this.OnTransactionRollingBack(EventArgs.Empty);
                }
            }
        }

        private void OnTransactionRollingBack(EventArgs e)
        {
            EventHandler handler = TransactionRolledBack;
            if(handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Events
{
    class Program
    {
        static void Main(string[] args)
        {
            var rollBack = new SqlRollback();
            rollBack.TransactionRolledBack += RollBack_TransactionRolledBack;

            try
            {
                rollBack.Transact();
            } catch (Exception e)
            {

            }
        }

        private static void RollBack_TransactionRolledBack(object sender, EventArgs e)
        {
            Console.WriteLine("Rooled!");
        }
    }
}

【讨论】:

  • 如果我没记错的话,实际上虽然command.ExecuteNonQuery() 会在超时时抛出异常,导致在我们有机会调用transaction.Rollback 之前开始回滚,之后我们可能两者都不能回滚或提交事务。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-14
  • 2021-08-21
  • 1970-01-01
  • 1970-01-01
  • 2017-10-12
  • 2016-04-13
  • 1970-01-01
相关资源
最近更新 更多