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

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

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

  1. using (myEntities db = new myEntities ())
  2. {
  3. db.Database.Connection.Open();
  4. try
  5. {
  6. using (var scope = db .Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
  7. {
  8. {
  9. var test = db.customer_table.Where(x => x.id == 38).FirstOrDefault();
  10. test.bank_holder_name = "CLIENT NAME XXXX";
  11. db.SaveChanges(); <=== CLIENT B stop here while client A still in progress. After CLIENT A finish commit, here will throw *Deadlock found error*"
  12. scope.Commit();
  13. }
  14. }
  15. }
  16. catch (Exception ex)
  17. {
  18. throw;
  19. }
  20. }

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

  1. using (myEntities db = new myEntities ())
  2. {
  3. db.Database.Connection.Open();
  4. try
  5. {
  6. using (var scope = db .Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
  7. {
  8. {
  9. 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
  10. db.SaveChanges();
  11. scope.Commit();
  12. }
  13. }
  14. }
  15. catch (Exception ex)
  16. {
  17. throw;
  18. }
  19. }

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

vlju58qv

vlju58qv1#

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

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

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

iyfjxgzm

iyfjxgzm2#

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

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

也就是说

  1. SET NOCOUNT ON;
  2. UPDATE [Customers] SET [bank_holder_name] = @p0
  3. WHERE [id] = @p1;
  4. SELECT @@ROWCOUNT;

相关问题