【问题标题】:How do SqlConnection and Dapper treat TransactionsSqlConnection 和 Dapper 如何处理 Transactions
【发布时间】:2021-08-20 06:50:15
【问题描述】:

我有多个服务器运行同一个应用程序(分布式容器)。该应用程序修改了一个 SQL Server 表,所以它的作用是检查一个表的标志(例如status = open),阻止其中一些(有一个 top(10) 用于性能问题)并处理那些打开的行。完成后,它会相应地设置标志(高度简化)

我现在遇到的问题是,一些服务器随机崩溃

事务在锁定资源上与另一个进程发生死锁,并已被选为死锁牺牲品

我认为这是 SQL Server 错误。所以我的问题: 开始时

using (var connection = new SqlConnection(connectionString))

和小巧玲珑,就像

connection.Query("my Query");

在使用结束之前,是否所有查询都像在一个 SQL 事务中一样处理?这里BeginTransaction 解决了问题还是将所有内容都视为嵌套事务?

真的很难测试,我的猜测是,这个问题每执行 10 次左右就会出现一次。整个过程需要处理对另一台服务器的 HTTP 请求,这些请求可能持续时间更长或更短,因此整个过程可能需要数百毫秒或几秒钟(我对此没有影响)。

更新

所以应用程序(无助于显示真实代码)会执行类似的操作

connection.Query(query1);
doOtherStuff
connection.Query(query2);
doOtherStuff
connection.Query(query3);
doOtherStuff 

其他东西是异步的(有时),但是,每个查询都可以独立执行,不需要处于关闭的事务中。

【问题讨论】:

  • 不幸的是,死锁并不是那么简单......您需要捕获并检查死锁图以查看其发生的位置和原因。长话短说,您永远无法完全防止死锁,因此您应该在发生死锁时进行某种形式的重试(并尽量避免死锁)。
  • 正如我所说,唯一 了解发生了什么的方法是查看死锁图...省去你自己的猜测 :)
  • @rst 所有操作都使用锁,只要连接打开,或者如果使用事务,只要事务处于活动状态。这与 SqlConnection 或 Dapper 无关,这就是数据库的工作方式。这就是为什么您需要尽快使用紧密连接的原因。 Dapper 或 SqlCommand 实际上都没有启动事务。 Dapper 不会关闭已经打开的连接。如果您的代码保持连接打开的时间过长,则会导致阻塞或死锁
  • 检查Using tables as Queues,尤其是Implementing a queue backed by a table is notoriously difficult, error prone and susceptible to deadlocks.这句话我了解到,最简单的解决方案是那篇文章中显示的破坏性阅读。 DELETE .. OUTPUT 将返回已删除的数据,本质上充当真实队列中的Dequeue
  • @rst 你还没有验证这是你想要的,或者你对“队列”使用了什么 SQL 技术。我从过去的痛苦经历中“猜测”。

标签: sql-server .net-core dapper sqlconnection


【解决方案1】:

这真的很棘手,我已经建立了EntityWorker.Core *并且这个问题已经在那里处理了。我想 Dapper 不会处理这些问题。

要首先了解问题,您可以阅读this

现在,在您查看并理解为什么会发生这种情况之后,您可以找到解决方法。

这是我解决这个问题的方法,看看下面的代码,让我知道你的想法和理解。

private static object internalChangeLocker = new object();
// here you should do db changes, this should be a general method only called by external calls.
public static int Save(object someObject) {
  lock(internalChangeLocker) {
    return internalSave(someObject);
  }
}

// this method should only be called from within Save or internalSave
private static int internalSave(object someObject, Transaction tran = null) {
  var tr = tran;
  if (tran == null)
    // then create and begin a new transaction here

    // here do the db operation here
    // you can also call internalSave(newobject, tr); agen and pass on the already created transaction.


    if (tran == null) {
      /// here you should then commit and end the transaction
      // you may ask why the check, this is importend becouse you may calls 
      // internalSave(newobject, tr); within the internalSave and have to use the same old transaction
    }
}

【讨论】:

  • 客户端锁不能解决任何类型的数据库并发问题。不过,它确实暗示了数据访问代码中的其他问题,例如重用连接。打开/关闭事务也不能解决死锁。
  • 是的,它确保一次只完成一个操作,尽管同时完成了许多操作调用。
  • 这意味着您的代码存在线程问题。数据库由多个客户端和连接使用,而不仅仅是一个。死锁通常发生在不同的客户端之间,而不是同一个应用程序之间。除了最小的网站外,所有网站都使用多个网络服务器,这意味着多个并发连接。即使在单个服务器上,每个 Web 请求也由不同的线程、不同的控制器实例和不同的连接提供服务。
  • 您想限制死锁吗?限制连接的寿命。只要锁处于打开状态,连接和事务都会保持锁处于活动状态。如果您只从长期连接中读取数据,共享锁将保持活动状态并阻止更新。对于事务,锁的寿命仅限于启动它们的事务。限制连接的寿命仍然更好,也更更便宜
  • 顺便说一句,那篇文章包含许多不准确之处和糟糕的建议,同时错过了真正解决死锁的事情。应用锁???阅读未提交???而且没有提到快照隔离????
猜你喜欢
  • 2021-09-09
  • 1970-01-01
  • 2015-12-27
  • 2017-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多