使用ef 6实体更新而不是executesqlcommand的死锁

2sbarzqh  于 2021-07-29  发布在  Java
关注(0)|答案(2)|浏览(413)

在我的数据库中处理并发:
客户端a更新一行
客户端b尝试更新同一行
客户端b需要等待客户端a提交其更新
两个客户端a和b示例都是模拟的,并使用以下代码:

using (myEntities db = new myEntities ())
 {
     db.Database.Connection.Open();

     try
     {
         using (var scope = db .Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
         {
             {  
                 var test = db.customer_table.Where(x => x.id == 38).FirstOrDefault();
                 test.bank_holder_name = "CLIENT NAME XXXX";
                 db.SaveChanges(); <=== CLIENT B stop here while client A still in progress. After CLIENT A finish commit, here will throw *Deadlock found error*"
                 scope.Commit();
             }
         }
     }
     catch (Exception ex)
     {
         throw;
     }
 }

这不是我所期望的,客户机b应该等待并且不允许查询任何关于row id=38的数据,但是它可以继续,直到 SaveChanges 最后抛出一个错误。
因此,我怀疑这可能是由linq(不正确的行/表锁)引起的
我的代码编辑如下:

using (myEntities db = new myEntities ())
 {
     db.Database.Connection.Open();

     try
     {
         using (var scope = db .Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
         {
             {  
                 var test = db.Database.ExecuteSqlCommand("Update customer_table set bank_holder_name = 'CLIENT XXXXX' where pu_id = 38"); <===== Client B is stop here and proceed after Client A is completed
                 db.SaveChanges();
                 scope.Commit();
             }
         }
     }
     catch (Exception ex)
     {
         throw;
     }
 }

最后,事务处理的是上面的代码(不是linq函数)。这太令人困惑了,linq在使事务工作不一致的行为背后做了什么?

vlju58qv

vlju58qv1#

这是由于ef代码生成了两个sql语句: SELECT 对于线路:

var test = db.customer_table.Where(x => x.id == 38).FirstOrDefault();

…以及随后的 UPDATE 对于 SaveChanges() 打电话。
对于可序列化的隔离级别,当 SELECT 语句已运行。当他们中的一个或另一个第一次尝试执行 UPDATE 它们无法获得所需的独占锁,因为另一个客户端上有一个共享锁。然后,另一个客户机本身尝试获取一个独占锁,这样就出现了死锁情况。
这个 ExecuteSqlCommand 只需要一个update语句,因此不会发生死锁。
可序列化的隔离级别可以大大降低并发性,这个示例正好说明了原因。您会发现,较不严格的隔离级别将允许ef代码工作,但存在虚记录、不可重复读取等风险。然而,这些风险很可能是您为了提高并发性而愿意承担和/或减轻的风险。

iyfjxgzm

iyfjxgzm2#

不要先获取实体。而是创建一个“存根实体”并更新它,例如

var test = new Customer() { id = 38 };
test.bank_holder_name = "CLIENT NAME XXXX";
db.Entry(test).Property(nameof(Customer.bank_holder_name)).IsModified = true;
db.SaveChanges();

也就是说

SET NOCOUNT ON;
  UPDATE [Customers] SET [bank_holder_name] = @p0
  WHERE [id] = @p1;
  SELECT @@ROWCOUNT;

相关问题