csjsias

博客园 首页 新随笔 联系 订阅 管理

为什么需要并发控制?

在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突。这就是著名的并发性问题。

典型的冲突有:

丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失。例如:用户A把值从6改为2,用户B把值从2改为6,则用户A丢失了他的更新。

脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取。例如:用户A,B看到的值都是6,用户B把值改为2,用户A读到的值仍为6

为了解决这些并发带来的问题。 我们需要引入并发控制机制,Nhibernate中可以使用乐观锁和悲观锁来控制数据冲突。

乐观锁

它如果检测到实体被改变,我们就不能更新它,让我们看一个简单的例子,乐观锁(dirty):

配置:

         <class  name="" table="Person" lazy="true" optimistic-lock="dirty" dynamic-update="true">

测试代码:

                            using (ITransaction tx = session.BeginTransaction())

                {

                    var person = session.Get<Person>(1);

                    person.Name = "other";

                    tx.Commit();

                }          

测试结果:

UPDATE Person SET Name = @p0

WHERE Id = @p1 AND Name = @p2;@p0 = 'other', @p1 = 1, @p2 = 'wang'

如果把optimistic-lock 参数变成All,那么测试结果是:

UPDATE Person SET Name = @p0

WHERE Id = @p1 AND Name = @p2 AND Country = @p3 AND ZipCode = @p4;@p0 = 'other2', @p1 = 1, @p2 = 'other', @p3 = 'China', @p4 = '310000'

 

这样就能防止冲突了? 是的,请看下面一个抛异常的例子:

 [Test]

        public void PersonConcurrencyTest()

        {

            using (ISession session = NHibernateHelper.GetSession())

            {

                using (ITransaction tx = session.BeginTransaction())

                {

                    var person = session.Get<Person>(1);

                    person.Name = "test01";

 

                    // 模拟另外一个用户修改数据

                    using (ISession session2 = NHibernateHelper.GetSession())

                    {

                        using (ITransaction tx2 = session2.BeginTransaction())

                        {

                            var person2 = session2.Get<Person>(1);

                            person2.Name = "test04";

                            tx2.Commit();//提交成功

                        }

                    }

                    tx.Commit();        //提交失败,数据冲突

                }

            }

        }

 

错误信息:

NHibernate: UPDATE Person SET Name = @p0 WHERE Id = @p1 AND Name = @p2 AND Country = @p3 AND ZipCode = @p4;@p0 = 'test01', @p1 = 1, @p2 = 'test02', @p3 = 'China', @p4 = '310000'

Test 'NHibernateSample.Data.Test.NHibernateSampleFixture.PersonConcurrencyTest' failed: NHibernate.StaleObjectStateException : Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [NHibernateSample.Domain.Entities.Person#1]

         at NHibernate.Persister.Entity.AbstractEntityPersister.Check(Int32 rows, Object id, Int32 tableNumber, IExpectation expectation, IDbCommand statement)

         ……….

        

第二次提交之所以失败是因为那行数据被另外一个事务(用户)修改了,这样我们就得到了StaleObjectException 错误,当前事务提交失败,seesion失效

考虑这样一个场景:用户小王从数据库中取得这条数据后展现在界面上,用户编辑了10分钟还喝了一杯茶,然后保存数据;期间数据被小张修改,小王是否可以检测到数据被修改(数据冲突异常)?

答案是不能

这里有一个更好的策略,使用显式的版本列,即在数据库中增加一列 Version

配置文件作相应修改:

<class name="" table="Person" lazy="true" optimistic-lock="version" dynamic-update="true">

增加版本控制列:

         <version name="Version" column="Version" type="Int32"   ></version>

当执行普通更新操作时,执行的sql如下:

UPDATE Person SET          Version = @p0, Name = @p1

WHERE Id = @p2 AND Version = @p3;@p0 = 1, @p1 = 'other2', @p2 = 1,@p3=0

我想你也猜到了,如果version 字段值与原来的不一样,就会抛StaleObjectException异常

 

Version字段是怎么被更新的?

这是Nhibernate的机制,当我们完成配置工作的时候(Person.hbm.xml,一旦person表有数据被更新,version字段会字段更新。

 

在某些情况下,比如数据库中的job,绕过Nhibernate机制,直接更新这个表的数据,那个version字段的值会被自动更新吗?

不会。

这可怎么办呢,做一下稍微的改动就可以避免。上面控制版本的字段version int类型,做如下修改:

<version name="Version" column="Version" type="timestamp"/>

该成timeStamp 类型,这个字段任何时候都会被更新只要有数据被更新。

悲观锁

Nhibernate中的悲观锁使用LockMode来传递。

比如:

using (ITransaction tx = session.BeginTransaction())

                {

                    var person = session.Get<Person>(1,LockMode.Upgrade);

                    person.Name = "other-Pessimistic2";

                    tx.Commit();

                }

 

SELECT person0_.Id as Id0_0_, person0_.Name as Name0_0_, person0_.Country

    as Country0_0_, person0_.ZipCode as ZipCode0_0_

FROM Person person0_ with (updlock, rowlock) WHERE

    person0_.Id=@p0;@p0 = 1

关于sql Lock 的更多信息可以参考微软技术文档(Table Hints):

http://msdn.microsoft.com/en-us/library/ms187373.aspx

各适用于什么样的场景?

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。 乐观锁不能解决脏读的问题

 

例子中用到的数据库结构:

CREATE TABLE [dbo].[Person](
 [Id] [int] NOT NULL,
 [Name] [nvarchar](100) NULL,
 [Country] [nvarchar](50) NULL,
 [ZipCode] [nvarchar](20) NULL,
 [Version] [int] NULL
)

posted on 2012-11-14 17:07  快乐121  阅读(515)  评论(0)    收藏  举报