目录
写在前面
上篇文章介绍了nhibernate中的事务,在增删改查中使用的必要性。本篇文章将介绍nhibernate中的并发控制。对多人同时修改同一条数据,如何进行并发控制,在nhibernate中提供了一些方法来实现乐观并发控制。
文档与系列文章
并发控制
什么是并发控制?
当很多人试图同时修改数据库中的数据时,必须有这样一种控制,使一个人的操作不对他人的操作产生负面影响,这就是并发控制。
说的更简单点就是,2个或者多个用户(实际用户,服务,多线程)同时编辑相同数据时,及其在连接或者断开情况下可能发生的情况。
并发控制理论根据控制方法而分为两类:乐观并发控制和悲观并发控制。
乐观并发控制(Optimistic Concurrency)
在乐观并发控制中,用户读取数据时不锁定数据。当一个用户更新数据时,系统将进行检查,查看该用户读取数据后其他用户是否又更改了该数据。如果其他用户更新了数据,将产生一个错误。一般情况下,收到错误信息的用户将回滚事务并重新开始。这种方法之所以称为乐观并发控制,是由于它主要在以下环境中使用:数据争用不大且偶尔回滚事务的成本低于读取数据时锁定数据的成本。(sql2008 MSDN)
NHibernate提供了一些方法实现乐观并发控制,在配置文件中:定义了<version>和<timespan>节点,其中<version>节点用于版本控制,表明数据表中数据的版本信息。<timespan>用于时间戳跟踪,表明数据表中包含时间戳数据。时间戳本质上是一种乐观锁定不太安全的实现。通常而言,版本控制方式是首选的方式。
看一下映射文件中version和timestamp节点的属性:
属性说明:
access(默认为property):Nhibernate用于访问特性值的策略。
column(默认为特性名):指定具有版本号或者时间戳的字段名。
gennerated:生成属性,可选never和always两个值。
name:持久化类的特性名或者指定类型为.NET类型DateTime的特性名。
type(默认Int32):版本号的类型,可选类型为Int64、Int32、Int16、Ticks、Timestamp、TimeSpan。注意:<timestamp>和<version type="timestamp">是等价的。
unsaved-value(在版本控制中默认是“敏感”值,在时间截默认是null):表示某个实例刚刚被实例化(尚未保存)时的版本特性值,依靠这个值就可以把这种情况和已经在先前的会话中保存或装载的游离实例区分开来。(undefined指明使用标识特性值进行判断).
一个例子
使用版本控制方式进行乐观并发控制,修改持久化类Customer,添加版本控制属性Version
1 ///2 /// 描述:客户实体,数据库持久化类 3 /// 创建人:wolfy 4 /// 创建时间:2014-10-16 5 /// 6 public class Customer 7 { 8 ///9 /// 客户id10 /// 11 public virtual Guid CustomerID { get; set; }12 ///13 /// 客户名字14 /// 15 public virtual string CustomerName { get; set; }16 ///17 /// 版本控制18 /// 19 public virtual int Version { get; set; }20 ///21 /// 客户地址22 /// 23 public virtual string CustomerAddress { get; set; }24 }
修改映射文件Customer.hbm.xml,添加version映射节点。
1 2 34 5 6 207 8 11 129 10 13 14 1615 17 1918
修改数据表TB_Customer,添加version字段,默认值为1。
alter table tb_customer add [Version] int not null default 1
并发更新控制
数据库中的数据有
测试同时修改客户id为“82724514-682E-4E6F-B759-02E499CDA50F”名字为“wanger”的客户信息,两个操作同时修改客户住址为“南京”和“黑龙江”。代码如下:
数据层代码
1 ///2 /// 通过事务的方式添加或者修改 3 /// 4 /// 添加的对象 5 ///是否成功 6 public bool SaveOrUpdateByTrans(Customer customer) 7 { 8 NHibernateHelper nhibernateHelper = new NHibernateHelper(); 9 var session = nhibernateHelper.GetSession();10 using (ITransaction transaction = session.BeginTransaction())11 {12 try13 {14 session.SaveOrUpdate(customer);15 session.Flush();16 //成功则提交17 transaction.Commit();18 return true;19 }20 catch (Exception)21 {22 //出现异常,则回滚23 transaction.Rollback();24 return false;25 }26 }27 }
测试数据
1 ///2 /// 并发更新操作 3 /// 4 /// 5 /// 6 protected void btnSameTimeUpdate_Click(object sender, EventArgs e) 7 { 8 Guid guidCustomerId = new Guid("82724514-682E-4E6F-B759-02E499CDA50F"); 9 //模拟第一个修改数据10 Customer c1 = new Customer()11 {12 CustomerID = guidCustomerId,13 CustomerAddress = "南京",14 CustomerName = "wanger",15 Version = 116 };17 //模拟第二个修改数据18 Customer c2 = new Customer()19 {20 CustomerID = guidCustomerId,21 CustomerAddress = "黑龙江",22 CustomerName = "wanger",23 Version = 124 };25 26 Business.CustomerBusiness customerBusiness = new Business.CustomerBusiness();27 customerBusiness.SaveOrUpdateByTrans(c1);28 customerBusiness.SaveOrUpdateByTrans(c2);29 }
修改后的数据
同对比上面的数据库中的数据,我们发现Version版本变成2了,而且修改的客户地址为“黑龙江”信息并没有修改成功。监控的sql发现,两次update确实都提交了,但是只有第一次修改了。如图:
细心的你可能会发现@p0的值在改变,并且在修改的时候多了一个Where子句
WHERE CustomerID = @p3 AND Version = @p4'
这就是为什么第二次为什么没有修改成功了,因为已经找不到这条数据了。
悲观并发控制(Pessimistic Concurrency)
一个锁定系统,可以阻止用户以影响其他用户的方式修改数据。如果用户执行的操作导致应用了某个锁,只有这个锁的所有者释放该锁,其他用户才能执行与该锁冲突的操作。这种方法之所以称为悲观并发控制,是因为它主要用于数据争用激烈的环境中,以及发生并发冲突时用锁保护数据的成本低于回滚事务的成本的环境中。
简单的理解通常通过“独占锁”的方法。获取锁来阻塞对于别的进程正在使用的数据的访问。换句话说,读者和写者之间是会互相阻塞的 ,这可能导致数据同步冲突。
总结
本文讲述nhibernate中乐观并发控制的版本控制方式,列举了一个并发修改的例子(很可能你会说那不是顺序执行的吗?你也看到这种顺序执行,也无法修改),在分析生成的sql语句中,你会发现如果加上版本控制,在修改的时候会在where子句中加上版本号。
参考文章: