首页新闻找找看学习计划

使用EF导致sql server死锁,求高手解答

0
悬赏园豆:5 [已解决问题] 解决于 2017-07-04 09:57

先看测试代码:

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var random = new Random().Next(1,10000);
 6             for (var i = 0; i < 2; i++)
 7             {
 8                 var thread = new Thread((o) =>
 9                 {
10                     var id = (int) o;
11                     Process(id);
12                 });
13                 thread.Start(i + random);
14             }
15             
16             Console.ReadLine();
17         }
18 
19         private static void Process(int accountId)
20         {
21             try
22             {
23                 using (var scope = new TransactionScope())
24                 {
25                     using (var db = new TestDbContext())
26                     {
27                         var nothing = db.MpUsers.FirstOrDefault(x => x.AccountId == -1);
28                         Thread.Sleep(1000);
29                         var user = new MpUser()
30                         {
31                             AccountId = accountId
32                         };
33                         db.MpUsers.Add(user);
34                         db.SaveChanges();
35                     }
36                     scope.Complete();
37                     Console.WriteLine(accountId + ": done");
38                 }
39             }
40             catch (DbUpdateException ex)
41             {
42                 Console.WriteLine(accountId + ": " + ex.InnerException.InnerException.Message);
43             }
44         }
45         
46 
47     }

 

上面的代码先执行查询(返回空),然后在执行插入,先执行的那个线程一定会抛异常。请问是为什么呢?

 

异常信息:事务(进程 ID 56)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。

 

但是,当把 

var nothing = db.MpUsers.FirstOrDefault(x => x.AccountId == -1);

替换为:

var nothing = db.MpUsers.FirstOrDefault(x => x.Id == -1);

之后,虽然都返回空,但是却不再报死锁的错误,两个线程都可以正确执行了。这又是为什么呢?

问题补充:

当把var nothing = db.MpUsers.FirstOrDefault(x => x.AccountId == -1);

替换为var nothing = db.MpUsers.FirstOrDefault(x => x.AccountId == 1);

因为找到了AccountId为1的记录,不返回空的时候两个线程又可以正确执行了。

Leo C.W的主页 Leo C.W | 初学一级 | 园豆:155
提问于:2017-06-28 18:18
< >
分享
最佳答案
0

不用TransactionScope的情况呢

收获园豆:5
吴瑞祥 | 高人七级 |园豆:28770 | 2017-06-28 19:37

 不用transactionscope不会报错

Leo C.W | 园豆:155 (初学一级) | 2017-06-28 20:50

@Leo C.W: 通过id查询是不会锁表的.因为有主键索引.

通过没有索引的字段查询.会锁表.

另外这个和ef没关系.是数据库锁的问题.

吴瑞祥 | 园豆:28770 (高人七级) | 2017-06-29 09:20

@吴瑞祥: 同样的数据结构,同样的代码,我用java hibernate连mysql就没有死锁的问题,难道是sql server这么弱吗?

Leo C.W | 园豆:155 (初学一级) | 2017-06-29 14:31

@Leo C.W: 说明mysql和hibernate的事务管理有问题.

死锁不死锁和数据库不应该有关系.是写sql的人操作不当导致.

应该死锁的地方不死锁.反而不好.

吴瑞祥 | 园豆:28770 (高人七级) | 2017-06-29 14:39

@吴瑞祥: 但上面的C#代码这种情况不应该死锁啊,因为这种需求很常见,我先从数据库查看是否已存在该username,不存在就插入新纪录。

Leo C.W | 园豆:155 (初学一级) | 2017-06-29 15:10

@Leo C.W: 所以你的问题处在不理解这个操作为什么死锁上.而我上面已经解释了.这个操作为什么会死锁.

想不让他死锁.别用TransactionScope就好.

PS:ef要实现这个逻辑很麻烦.得先读.再写.然后再读.然后有多条再保留最后一条删除前面的

mysql差不多也是这样的.sqlserver有一个专门的mergin指令实现这个逻辑 

吴瑞祥 | 园豆:28770 (高人七级) | 2017-06-29 15:19

@吴瑞祥: 我现在反正是明白了,只要transactionscope里面有非主键查询,并且查询结果返回null,同时又有增/删/改,那就一定会死锁(多线程同时操作时)。但是就想搞明白其中的内部逻辑。大神有没有推荐的文章可以拜读一下?

Leo C.W | 园豆:155 (初学一级) | 2017-06-29 15:27

@吴瑞祥: 或者大神能不能写篇博文,专门研究transactionscope是如何导致死锁的,以及最佳解决之道?

Leo C.W | 园豆:155 (初学一级) | 2017-06-29 15:28

@Leo C.W: 我上面不是说了.

你用的事务级别的问题.如果没有索引.他会全表扫描.所以会把整个表锁起.

没什么复杂的原因.关键词就是:事务/事务级别/索引/全表扫描/索引对锁的影响.

吴瑞祥 | 园豆:28770 (高人七级) | 2017-06-29 15:38

@吴瑞祥: 好像跟索引没有关系哦,根据我的测试,只要不是在主键上的查询,就死锁,包括在其他索引列上的查询。

Leo C.W | 园豆:155 (初学一级) | 2017-06-29 15:47

@Leo C.W: 你详细的看下事务级别的文档吧.里面有讲.什么时候锁什么.上面关于索引的说法确实是不对.

吴瑞祥 | 园豆:28770 (高人七级) | 2017-06-29 15:52
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册