ADO.NET 僵尸事务错误?如何保证命令不会在隐式事务上执行?

发布于 2024-09-04 04:40:48 字数 777 浏览 1 评论 0原文

例如,当发生死锁时,以下SQL 命令会成功执行,即使它们分配了回滚后的SQL 事务。看起来,这是由 SQL Server 上创建的新隐式事务引起的。

有人可能期望 ADO.NET 会抛出异常,表明命令正在僵尸事务上执行。但是,不会抛出此类异常。 (我认为这是 ASP.NET 中的一个错误。)此外,由于僵尸事务,最终的 Dispose() 会默默地忽略回滚。

有什么想法,我如何确保没有人可以在隐式事务上执行命令? 或者,如何检查该交易是僵尸交易?我发现 Commit()Rollback() 检查僵尸事务,但是我可以调用它们进行测试:)

我还发现读取 IsolationLevel 也会进行检查,但我不确定简单的调用 transaction.IsolationLevel.ToString(); 是否不会被未来的优化器删除。或者您是否知道任何其他安全的方法来调用 getter(不使用反射或 IL 发射)?

编辑:Remus Rusanu 指出这种情况通常不会发生。是的,确实如此。当代码中存在错误时通常会发生这种情况。在我们的例子中,finally 语句中有一些日志记录例程试图将失败存储到数据库中。现在,我正在尝试找到一种解决方案,以便将来检测此类错误。由于这些错误很难测试。如果 ADO.NET 检查所提供的事务是否为僵尸事务,则将更容易发现此错误。我发现了两种可能性:

  1. 关闭隐式事务的创建 - 我不确定这是否可能。
  2. 确保在执行任何命令之前,检查僵尸事务是否会运行。

e.g. When deadlock occurs, following SQL commands are successfully executed, even if they have assigned SQL transaction that is after rollback. It seems, it is caused by a new implicit transaction that is created on SQL Server.

Someone could expect that ADO.NET would throw an exception that the commands are being executed on a zombie transaction. However, such exception is not thrown. (I think this is a bug in ASP.NET.) Moreover, because of zombie transaction the final Dispose() silently ignores the rollback.

Any ideas, how can I ensure that nobody can execute commands on implicit transaction?
Or, how to check that transaction is zombie? I found that Commit() and Rollback() check for zombie transaction, however I can call them for a test:)

I also found that also reading IsolationLevel will do the check, but I am not sure whether simple calling transaction.IsolationLevel.ToString(); will not be removed by a future optimizer. Or do you know any other safe way invoke a getter (without using reflection or IL emitting)?

EDIT: Remus Rusanu pointed out that this situation would not normally occur. Yes, that's true. It usually occurs when there is a bug in a code. In our case, there was some logging routine in a finally statement that was trying to store the failure into the database. Now, I am trying to find a solution how to detect such bugs in a future. Since these bugs are difficult to test. If ADO.NET would check that the provided transaction is zombie, this bug will be found much more easily. I found two possibilities:

  1. Turn off creation of implicit transactions - I am not sure whether this is possible.
  2. Ensure that before executing any commands, check for zombie transaction will run.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

往日 2024-09-11 04:40:49

你所描述的情况不存在。事务回滚将在您的应用程序中引发非常明显的异常。出于某种原因,我更相信您的代码捕获了异常并默默地忽略它,在事务回滚后继续执行语句。

What you describe doesn't exists. A transaction rollback will throw a very visible exception in your application. for some reason, I much rather believe your code captures the exception and silently ignores it, continuing to execute statements after the transaction had rolled back.

如此安好 2024-09-11 04:40:49

可能与您的问题没有直接关系,因为它是由错误引起的,但仍然可能令人感兴趣。并非所有错误都会导致事务回滚,因此有时事务可能“部分成功” - 某些语句出错,而其他语句则正常完成。
有一个选项 SET XACT_ABORT ON 使服务器中止事务任何错误。

考虑到你的问题,你不能关闭隐式事务(如果你执行一条SQL语句,除非另一个事务已经处于活动状态,否则将创建一个隐式事务)。因此,您只需正确处理错误,以确保在您需要时有交易存在。
看一下 TransactionScope 类,您可以使用它以避免在代码中管理这些事务。

Probably not directly related to your problem, as it was caused by a bug, but still might be of interest. Not all errors cause a transaction to rollback, so sometimes a transaction might be "partially successful" - some statements errored while others compeleted fine.
There is an option SET XACT_ABORT ON that makes the server abort transaction on any error.

Considering your question, you cannot turn off implicit transactions (if you execute an SQL statement, an implicit transaction will be created unless another transaction is already active). So you just have to handle the errors correcty to make sure that a transaction is there when you need it.
Have a look at TransactionScope class, you can use it to avoid managing these transactions in your code.

小瓶盖 2024-09-11 04:40:49

//根据您的描述,我猜测您的代码有效地执行了此操作,

        SqlConnection conn = new SqlConnection("ConnectionString");
        SqlCommand cmd = new SqlCommand("insert into ....");

        cmd.Connection = conn;

        conn.Open();

        SqlTransaction tran = conn.BeginTransaction();


        cmd.Transaction = tran;

        tran.Rollback(); //or tran.Dispose();

        cmd.ExecuteNonQuery();

这会导致 cmd 在事务范围之外执行。

删除行 cmd.Connection = conn;将实现我认为您正在寻找的行为(例如,命令将失败,因为事务不再有效。)

SqlConnection conn = new SqlConnection("ConnectionString");
    SqlCommand cmd = new SqlCommand("insert into ....");

    //cmd.Connection = conn;

    conn.Open();

    SqlTransaction tran = conn.BeginTransaction();
    cmd.Connection = tran.Connection;

    cmd.Transaction = tran;

    tran.Rollback(); //or tran.Dispose();

    cmd.ExecuteNonQuery();

//Based upon your description I'm guessing that your code effectively does this

        SqlConnection conn = new SqlConnection("ConnectionString");
        SqlCommand cmd = new SqlCommand("insert into ....");

        cmd.Connection = conn;

        conn.Open();

        SqlTransaction tran = conn.BeginTransaction();


        cmd.Transaction = tran;

        tran.Rollback(); //or tran.Dispose();

        cmd.ExecuteNonQuery();

This causes the cmd to be executed outside the scope of a transaction.

Removing the line cmd.Connection = conn; will achieve the behavior that I think you're looking for (e.g. the command will fail because the transaction is no longer valid.)

SqlConnection conn = new SqlConnection("ConnectionString");
    SqlCommand cmd = new SqlCommand("insert into ....");

    //cmd.Connection = conn;

    conn.Open();

    SqlTransaction tran = conn.BeginTransaction();
    cmd.Connection = tran.Connection;

    cmd.Transaction = tran;

    tran.Rollback(); //or tran.Dispose();

    cmd.ExecuteNonQuery();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文