NHIbernate学习之旅【五】——并发处理

  并发我之前略有研究过一点,但没实际应用过,这里先简单说说并发,在讲讲NHIbernate的并发机制。

      并发是中大型网站常常会遇到的问题,如果碰到并发情况,又没有在程序里面进行控制可能会给网站带来不小的损失。比如:

  一张表里面的一个字段的数据是;122446,在同一时刻有两人都读取了这个数据,并准备进行修改,这样会出现并发情况,后果会怎么样呢?

    A读取到122446

    B读取到122446

    A发现2重复了,并且没有3,于是把2改成3,并进行数据提交了。

    B发现4重复了,并且没有5,于是把4改成5,并进行数据提交了。

    这样A是先提交的,B是后提交的,B就会把A的数据覆盖,本来理想结果是123456的,但由于覆盖了,最终是122456。

  以上就是并发产生的危害之一——两次更新问题。根据并发出现的情况会产生以下4中常见级别错误,我们统称为事务隔离级别: 

 在JDBC操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别的概念。

   问题的提出 :数据库是要被广大客户所共享访问的,那么在数据库操作过程中很可能出现以下几种不确定情况。

  ● 更新丢失(Lost update):两个事务都同时更新一行数据,但是第二个事务却中途失败退出,导致对数据的两个修改都失效了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。

  ● 脏读(Dirty Reads):一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚。

  ● 不可重复读(Non-repeatable Reads):一个事务对同一行数据重复读取两次,但是却得到了不同的结果。例如,在两次读取的中途,有另外一个事务对该行数据进行了修改,并提交。

  ● 两次更新问题(Second lost updates problem):无法重复读取的特例。有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。

  ● 幻读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。

  事务隔离级别隔离级别不是我们现在要谈论的,我们这篇主要谈论的是NHibernate的并发处理机制,因此我们重点看两次更新问题,常见的处理方式是排它锁(悲观锁),或者是版本控制(乐观锁),NHibernate就是用的乐观锁。这里简单介绍下乐观锁和悲观锁,悲观锁具有独占性,如果A用户在改一条数据,B用户是改不了的,只有等A改完了B才能改;那么乐观锁呢,这是我们主要讨论的重点。

  乐观锁是允许同时修改的,但同时修改就会引发并发问题,那乐观锁怎么解决的呢?这里采用了数据版本 ( Version )记录机制实现,就是在数据库中加上Version 字段 INT型,来控制修改版本。还是刚才的例子,但是我们加上version字段

    当数据为122446时,version=1,

    A读取到122446  version=1

    B读取到122446  version=1

    A发现2重复了,并且没有3,于是把2改成3,并进行数据提交了  version=2。

    因为2>1所以A修改成功。

    B发现4重复了,并且没有5,于是把4改成5,并进行数据提交了  version=2。

    因为2<=2所以B修改失败,返回报错。

  上面的例子没说明,大家可能没看懂,就是:默认一开始版本为1,同时两个人取数据,当A更新时,由于改变了一次版本所以v+1,而2>1,符合更新要求,所以更改成功。此时B更改,B和A取到的是同一条数据,所以此时V还是1,B更新时V+1=2,但由于A改过了(v=2),此时B的版本2=当前数据库中的V,所以不符合要求,更新失败。如果此时又有第三个人改,他取到的是2,更改之后版本就会是3,以此类推。你可能会问为什么B的版本和数据库的一致就失败了,因为更改版本之后如果还是数据库的版本一样是不可能的,只可能大于数据库当前版本才叫更新。在取个直观的例子

用户帐户信息的例子,假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。

  1 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。

  2 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。

  3 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。

  4 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须大于记

  录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。

  这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。  

  通过以上,大家对并发有了一定了解,接下来我们讲讲NHibernate的并发机制。

   首先,还是以前的项目,先把数据库CUSTOMER表加上Version字段 int类型 默认为1。同时在实体类customer.cs中添加public virtual int Version { get; set; },再在映射文件中添加version映射,nhibernate是通过<version>节点来进行版本控制的。

  

代码
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
   assembly
="NHibernateSample.Domain"
   
namespace="NHibernateSample.Domain.Entities">

    
<class name ="Customer">
        
<id name="Id" column ="CustomerId">
            
<generator class ="native"/>
        
</id>
        
<version name="Version" column="Version" type="integer" unsaved-value="0"/>
        
<property name ="FirstName"/>
        
<property name ="LastName"/>
    
</class>
</hibernate-mapping>

 

  注意version只能写在ID节点下面,不然会出错。

  这样接OK了。

  在UI层,新加一个并发按钮,代码如下:

  

代码
        protected void Button4_Click(object sender, EventArgs e)
        {
            Customer c1 
= cr.GetCustomerById(1);
            Customer c2 
= cr.GetCustomerById(1);

            c1.FirstName 
= "one";
            c2.FirstName 
= "two";

            cr.UpdateCustomer(c1);
            cr.UpdateCustomer(c2);
        }

 

  

  点击按钮,你会发现version=2了,再次点击version还是2,什么原因?前面我们说的应该等于3呀,这个问题一直也困扰着我,查了不少资料,最后个人认为:更新的数据和数据库里面的数据一致的话NHibernate会不进行更新的。你吧one two改成1 2之后再点击按钮,版本就变成3了,这样就实现了BHibernate的并发处理。

posted on 2010-05-07 14:26  neekey  阅读(1274)  评论(1)    收藏  举报

导航