防弹方法可以解决SQL交易超时



最近我们面临着一个非常有趣的问题,这与SQL Transactions超时有关。出于问题的目的,该陈述的说明并不重要,但它是与客户生成的GUID的明确交易的单一插入语句:

INSERT MyTable
(id, ...)
VALUES (<client-app-generated-guid>, ...)

我们也有一个就地重试策略,因此,如果命令失败,则将重述。SQL Server(Azure SQL)通常没有一天的行为,我们在重试期间遇到了许多奇怪的PK违规错误。它们是由重试实际上成功地在SQL Server Transaction上成功引起的(这会导致插入ID的插入)。我知道SQL超时是纯粹的客户端概念,因此,如果客户端认为SQLCommand失败了 - 可能是或可能不是的意思。

我怀疑客户通过TransactionScope包装语句的明确交易控制,如所示,Bellow将解决此类麻烦的99% - 因为提交实际上很快,便宜的操作。但是,我仍然看到那里的警告 - 超时也可以在委托阶段发生。该应用程序再次可能在不可能猜测交易是否真正承诺的情况下(找出重试的必要性)。

问题是如何在Bulletproof中编写代码(这种麻烦)和 generic 时尚,并且只有在确定不进行交易的情况下才进行重试。

using (var trx = new TransactionScope())
using (var con = GetOpenConnection(connectionString))
{
    con.Execute("<some-non-idempotent-query>");
    // what if Complete() times out?!
    // to retry or not to retry?!
    trx.Complete();
}

问题是例外并不意味着事务失败。对于任何补偿措施(例如重试),您需要有一种确定的方法来确定它是否失败。我建议的内容存在可伸缩性问题,但是它的技术是重要的,可以通过其他方式解决可伸缩性问题。

我的解决方案;

  1. 提交之前的最后一个插入物是将GUID写入跟踪表。
  2. 如果发生异常,则指示网络故障,选择@@ trancount。如果它表明您仍在交易中(大于0)(可能永远不会发生,但值得检查),那么您可以愉快地重新提交您的提交
  3. 如果@@ trancount返回0,那么您将不再进行交易。从跟踪表中选择GUID将告诉您您的提交是否成功。
  4. 如果您的提交未成功(@@ trancount == 0,并且您的GUID在跟踪表中不存在),然后从开始交易中重新提交整个批次。

一般方法是:尝试读取刚刚尝试插入的内容。

如果您可以读回尝试插入的ID,则成功进行了以前的交易,无需重试。

如果找不到尝试插入的ID,那么您知道您的尝试插入失败了,因此您应该重试。


我恐怕没有办法拥有完全通用的模式,可以适用于任何SQL语句。您的"检查"代码需要知道要寻找什么。

如果是带有ID的INSERT,则您正在寻找该ID。

如果是一些UPDATE,则检查将是自定义的,并取决于该UPDATE的性质。

如果是DELETE,则支票包括尝试读取要删除的内容。


实际上,这是一个通用模式:任何具有一个或多个INSERTUPDATEDELETE语句的数据修改批次应该在该交易中还有更多INSERT语句,该声明将某些GUID(数据修改事务本身的某些ID)插入专用审核表。然后,您的检查代码试图从该专用审核表中读取相同的GUID。如果找到了GUID,那么您知道以前的交易成功完成。如果找不到GUID,那么您知道先前的交易已退回并可以重试。

让此专用审核表统一/标准化检查。这些检查不再取决于内部设备和数据更改代码的详细信息。您的数据修改代码和验证代码取决于相同的商定接口-Audit表。

最新更新