并发分为两种,一种叫做悲观并发,一种叫乐观并发。 名字挺文艺


悲观并发
悲观并发是什么呢? 就拿我们常用的代码版本控制来说。 有一个文档,A和B都要 获取这个文档并进行修改, 如果当A在读取这个文档数据时,就单独霸占了这个文档,B无法获取,只有当A读取修改完毕释放锁时,B才能获取这个文件,也就是一个人去的控制权的时候,其他人只能等待。这就是悲观锁。因为担心,多人同时操作造成的数据紊乱,大概是因为建立在这样的心态上,取名 “悲观锁”的。
悲观锁 通常用在频繁发生数据竞争的激烈环境下,以及 通过锁来保护数据所需的成本小于回滚事务的成本的时候。
 
乐观并发
乐观并发显然,“心理”上是反过来的,就是 “我不担心A和B同时取得控制权,A和B可以同时读取 同时修改,但是A修改保存后,B再修改保存,这时候系统会发现,当前文档和B进入系统时不一样了,就会报错。这显然也是一种不错的处理方式。
乐观锁 应用场景一般是 数据竞争并不是特别激烈,且 偶尔的数据争执所需的回滚事务的成本小于读取数据时锁定数据所需要的成本。乐观并发的初衷在于,不希望经常性的看到数据争执。
 
悲观并发如果加锁的成本较高的话,会很明显的降低效率,降低系统的并发性。
乐观并发 通常分为 三个阶段 读取阶段--校验阶段--写入阶段
在读取阶段,A和B分别将数据读入各自机器缓冲,此时并没有校验,在校验阶段,系统事务会对文件进行同步校验,如果不出现问题,则进入第三阶段写入,数据最终被提交,否则报错。
 
悲观锁还有一个常见的问题就是“死锁”,比如文档T1 和T2是内容相关的,但是不巧 T1被A锁住了 T2有被B锁住了, 那就会卡在这直到有一方先取消锁。
 

我们先看一下不控制并发时的场景
  1. //未进行并发处理
  2. User user = new User { UserName="shenwei" ,certID= ""};
  3. using (BlogContext ctx= new BlogContext())
  4. {
  5. ctx.Users.Add(user);
  6. ctx.SaveChanges();
  7. } //首先插入一条数据 并提交
  8. //定义两个context同时进行操作
  9. BlogContext firContext = new BlogContext ();
  10. User u1 = firContext.Users.FirstOrDefault();
  11. BlogContext secContext = new BlogContext ();
  12. User u2 = secContext.Users.FirstOrDefault();
  13. u2.UserName = "zhangxiaomao" ; //改变名字 并提交
  14. secContext.SaveChanges();
  15. u1.UserName = "xxxxxx" ;
  16. u1.certID = "" ; //另一个操作改变certid,也提交
  17. firContext.SaveChanges();
数据库 查询select * from Users ;
 
回到我们的EF codefirst . EntityFramework只支持乐观并发,也就是说EF其实并不希望经常性的看到数据冲突。
 
针对整条记录的并发
EF实现并发控制 需要借助 TimeStamp 标示 ,并且一个类只能有 一个此标示,标示的必须是byte[]类型
  1. public class Blog
  2. {
  3. public string ID { get; set; }
  4. public string BlogName { get; set; }
  5. public string BlogAuthor { get; set; }
  6. public virtual List <Post> Posts { get; set ; } //导航属性
  7. public BlogDetails Detail { get; set; }
  8. [ Timestamp]
  9. public byte [] version { get; set; }
  10. }

测试如下

  1. //并发模拟
  2. Blog b = new Blog
  3. {
  4. ID = "",
  5. BlogName = "Gaea",
  6. BlogAuthor = "shenwei",
  7. Detail = new BlogDetails { }
  8. };
  9. //先通过一个ctx插入数据并提交
  10. using(BlogContext context=new BlogContext())
  11. {
  12. context.Blogs.Add(b);
  13. context.SaveChanges();
  14. }
  15. //创建一个ctx取的第一条数据,修改 但是不提交
  16. BlogContext fircontext = new BlogContext();
  17. Blog firstblog = fircontext.Blogs.FirstOrDefault();
  18. firstblog.BlogName = "哈哈,被改掉了" ;
  19.  
  20. //创建另一个ctx还是取第一条数据,修改并提交
  21. BlogContext secContext = new BlogContext();
  22. Blog secondBlog = secContext.Blogs.FirstOrDefault();
  23. secondBlog.BlogAuthor = "JasonShen";
  24. secContext.SaveChanges();
  25.  
  26. //这个时候再提交第一个ctx所做的修改
  27. try
  28. {
  29. //这是后会发现现在的数据,已经和刚进入时发生了变化,故报错
  30. fircontext.SaveChanges();
  31. Console.WriteLine("保存成功" );
  32. } catch(Exception e)
  33. {
  34. Console.WriteLine("保存失败" );
  35. }
  36. Console.ReadKey();
  37. }
结果如下
select * from Blogs;
之所以能捕捉到错误 是因为 EF这里的操作机制是将 被Timestamp标识的字段加入 where子句。
一开始插入一条数据之后,时间戳是这样的,初始版本的对象也就是这个样子
后来两个context 各自获取这个对象,其中一个进行修改,并提交,这个时候数据库中的时间戳标示的字段已经发生了改变。
这个时候 ,另一个context提交的时候 执行update .... where version=‘初始版本的version’ 然后会发现找不到,于是就报错!
也就是说依靠 timespan标示的字段来确认是否与初始版本发生了改动,若发生了,就报错,进行错误处理。
 

那如果捕捉到了异常,EF会怎么处理呢?使用Reload处理

Resolving optimistic concurrency exceptions with Reload

  使用Reload数据作为解决乐观并发异常的策略之一,除了Reload外,还有其他几种冲突解决策略,这里只讲下常用的Reload

  微软Entity Framework 团队 推荐处理乐观并发冲突的策略之一是Reload数据,也就是EF检测到并发冲突时会抛出DbupdateConcurrencyException,这时解决冲突分为Client Wins或者Store Wins ,而Reload处理也就是Store Wins,意味着放弃当前内存中的实体,重新到数据库中加载当前实体,EF官方团队给出来的示例代码如下
捕捉到异常的时候
  1. try
  2. {
  3. //这是后会发现现在的数据,已经和刚进入时发生了变化,故报错
  4. fircontext.SaveChanges();
  5. Console .WriteLine("保存成功" );
  6. } catch (DbUpdateConcurrencyException e)
  7. {
  8. Console .WriteLine("保存失败" );
  9. Console .WriteLine("Reload" );
  10. e.Entries.Single().Reload();
  11. Console .WriteLine(firstblog.BlogName); //会发现 变成了初始从数据库里加载的数据值
  12. }

针对单个字段的并发
 有些时候并不需要控制针对整条记录的并发,只需要控制某个列的数据 不会出现脏操作就ok
这个时候 就使用ConcurrencyCheck 标示
  1. public class User
  2. {
  3. [Key ,DatabaseGenerated (DatabaseGeneratedOption .Identity)]
  4. public Guid UserGuid { get; set; }
  5. public string UserName { get; set; }
  6. [ ConcurrencyCheck ]
  7. public string certID { get; set; }
  8. }
  1. //针对单个字段 标示的ConcurrencyCheck 的并发
  2. User user = new User { UserName = "shenwei" , certID = "" };
  3. using (BlogContext ctx = new BlogContext ())
  4. {
  5. ctx.Users.Add(user);
  6. ctx.SaveChanges();
  7. } //首先插入一条数据 并提交
  8. //定义两个context同时进行操作
  9. BlogContext firContext = new BlogContext ();
  10. User u1 = firContext.Users.FirstOrDefault();
  11. BlogContext secContext = new BlogContext ();
  12. User u2 = secContext.Users.FirstOrDefault();
  13. u2.certID= "" ; //改变名字 并提交
  14. secContext.SaveChanges();
  15. try
  16. {
  17. u1.certID = "" ; //另一个操作改变certid,也提交
  18. firContext.SaveChanges();
  19. } catch (Exception e)
  20. {
  21. Console .WriteLine("并发报错" );
  22. }
当然可以同时用concurrentCheck标示多个字段,那被标示的每个都不能被同时修改了。这里背后的机制同样是where将被标示的字段作为了筛选条件。
 

总结
经过分析乐观锁 并不适合处理高并发的场景,少量的数据冲突才是乐观并发的初衷。 悲观锁同样也不适合处理高并发,特别在加锁成本比较大的时候。
如果项目并发量确实大, 那就可以考虑采用其他技术实现,比如 消息队列等。
 
如果您喜欢这篇文章,欢迎推荐!
 
 

EF CodeFirst(三) 并发处理的更多相关文章

  1. 1.【使用EF Code-First方式和Fluent API来探讨EF中的关系】

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/relationship-in-entity-framework-using-code-firs ...

  2. [.NET领域驱动设计实战系列]专题一:前期准备之EF CodeFirst

    一.前言 从去年已经接触领域驱动设计(Domain-Driven Design)了,当时就想自己搭建一个DDD框架,所以当时看了很多DDD方面的书,例如领域驱动模式与实战,领域驱动设计:软件核心复杂性 ...

  3. EF CodeFirst增删改查之‘CRUD’

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精    本篇旨在学习EF增删改查四大操作 上一节讲述了EF ...

  4. EF CodeFirst 创建数据库

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精    话说EF支持三种模式:Code First   M ...

  5. 新年奉献MVC+EF(CodeFirst)+Easyui医药MIS系统

    本人闲来无事就把以前用Asp.net做过的一个医药管理信息系统用mvc,ef ,easyui重新做了一下,业务逻辑简化了许多,旨在加深对mvc,ef(codefirst),easyui,AutoMap ...

  6. 新年奉献MVC+EF(CODEFIRST)+EASYUI医药MIS系统(转)

    出处:http://www.cnblogs.com/chenlinzhi/p/4332628.html 本人闲来无事就把以前用Asp.net做过的一个医药管理信息系统用mvc,ef ,easyui重新 ...

  7. ASP.NET MVC深入浅出(被替换) 第一节: 结合EF的本地缓存属性来介绍【EF增删改操作】的几种形式 第三节: EF调用普通SQL语句的两类封装(ExecuteSqlCommand和SqlQuery ) 第四节: EF调用存储过程的通用写法和DBFirst模式子类调用的特有写法 第六节: EF高级属性(二) 之延迟加载、立即加载、显示加载(含导航属性) 第十节: EF的三种追踪

    ASP.NET MVC深入浅出(被替换)   一. 谈情怀-ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态 ...

  8. 第四节:EF Core的并发处理

    1.说明 和EF版本的并发处理方案一致,需要知道乐观并发和悲观并发的区别,EF Core只支持乐观并发:监控并发的两种方案:监测单个字段和监测整条数据,DataAnnotations 和 Fluent ...

  9. EF CodeFirst简介、默认约定、数据库初始化策略

    CodeFirst 工作流程 创建或修改领域类-->使用数据注解或者Fluent API来配置领域类-->使用自动数据库迁移技术或者基于代码的数据库迁移技术来创建数据库. CodeFirs ...

随机推荐

  1. C/C++掌握技能(二)

    多组输入:

  2. cpu 亲和性 affinity

    http://www.ibm.com/developerworks/cn/linux/l-affinity.html

  3. python with用法举例

    我们知道在操作文件对象的时候可以这么写 with open('a.txt') as f: '代码块' 上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明_ ...

  4. java基本语法三

    1 程序流程控制 流程控制语句是用来控制程序中各语句执行顺序的语句,可以将语句组合完成能完成一定功能的小逻辑模块. 流程控制方式采用结构化程序设计中规定的三种基本流程,即: ①顺序结构: 程序从上到下 ...

  5. 输入两棵二叉树A,B,判断B是不是A的子结构(c++实现)

    #include <iostream> #include <cstdio> #include <stdio.h> #include <string> # ...

  6. JavaScript -- Window-Name

    -----027-Window-Name.html----- <!DOCTYPE html> <html> <head> <meta http-equiv=& ...

  7. 百度2015校园招聘面试题回忆录(成功拿到offer)

    引言 盼望着,盼望着……今年终于轮到我找工作了,还深深记得去年跟在师兄后面各种打酱油的经历,当时觉得找工作好难啊,怎么面一个败一个,以后还能找到工作不? 不过当时的失败也是理所当然的,那时候没有做任何 ...

  8. mysql 主键和唯一索引的区别

    主键是一种约束,唯一索引是一种索引,两者在本质上是不同的. 主键创建后一定包含一个唯一性索引,唯一性索引并不一定就是主键. 唯一性索引列允许空值,而主键列不允许为空值. 主键列在创建时,已经默认为非空 ...

  9. Nginx缓存配置之手动清除缓存

    访问我的博客 前言 前文介绍了利用 nginx 的 nginx_ngx_cache_purge 模块来实现缓存功能,并设置了缓存时间为一天. 但是如果前端修改了页面,比如首页,由于 Nginx 缓存的 ...

  10. 【详解】JNI (Java Native Interface) (三)

    案例三:C代码访问Java对象的实例变量   获取对象的实例变量的步骤: 1. 通过GetObjectClass()方法获得此对象的类引用 2. 通过类引用的GetFieldID()方法获得实例变量的 ...