听棠.NET

用积极乐观的心态,面对压力
posts - 291, comments - 10629, trackbacks - 112, articles - 5
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

SmartPersistenceLayer 3.1.0.0 并发处理篇

提出问题

       数据并发问题不是新问题了。当两个用户都读取了同一条记录后,A用户进行了更新,B用户在更新时,有可能会覆盖A用户的修改,这就是典型的并发问题。

   这里有一篇MS的关于并发问题的文章:介绍 ADO.NET 中的数据并发[]

   我个人感觉最好的解决并发问题的方式是采用“时间戳TimeStamp”,这是由Sql Server数据库自行维护的,可以减少程序的管控,而且效率也相当高。

   采用时间戳的简单步骤:

在表中字义一个字段类型为TimeStamp即可,如字段TS

然后在更新与删除时进行TS的比较:
       Update tablename set …. Where key=@key and ts=@ts

   Delete from tablename Where key=@key and ts=@ts

然后根据Command执行返回的影响条数判断是否成功。

采用TimeStamp的好处就是此TimeStamp的值不需要程序维护,在Insert时会自动插入新值,在Update时,也会自动更新,因此从这一点上,TimeStamp是非常可取的。

 

但这个时间戳字段不是所有的数据库都有的。比如Access或是其他的,就算有的数据库也有时间戳,可能使用规则与Sql Server也不同,因此,如果系统是建在ORM的开发模式上,系统的异构数据库移植就会存在问题,因此,对于系统来说,要有应付所有数据库并发问题的机制。

 

分析问题

   为了寻找一个通用数据库都能采纳的并发处理机制,我们可以为表专门建一个字段,用来控制数据的并发:

   Insert时,插入新值

   Update时,进行旧值的比较,如果成功,也要更新此字段值

   Delete时,进行旧值比较,如果不同,则不成功

   这样的控制方式跟SQL Server的差不多,但需要手动维护此字段值,而这个操作,我们就可以交给ORM的持久层来完成。

   关于此值采用什么样的值?

   此字段值要求具有唯一性,在任何情况下都不能产生相同的值,或者说几乎不可能产生相同的值,自然会想到GUID,但我觉得GUID数值太大,不利于数值比较,所以决定采用System.DateTime.Now.Ticks这是一个12位的数字, 此属性的值为自 0001 1 1 日午夜 12:00 以来所经过时间以 100 毫微秒为间隔表示时的数字。因此发生相同值的可能性也相当小,比GUID更合适。

 

解决问题

   SPL(SmartPersistenceLayer)为了.NET持久层,在3.1.0.0版本中添加如上所述的并发处理机制。

   SmartPersistenceLayer的原理

     给表中定义一个字段为“时间戳”。

     Insert时会自动根据System.DateTime.Now.Ticks产生新值,插入到新记录中

     Update时,比较旧值是否相等,如果相等,则生成新的Ticks更新“时间戳”字段值,因此可以根据Update影响的条数来判断是否有并发错误。

     Delete时,比较旧值是否等,也可以通过影响条数来判断。

     在事务处理时,有个属性叫“是否强行执行”

     If(强行执行)

      {

                     则在处理过程中,就算遇到“影响条数为零”也会提交事务

}else

{

    如果遇到“影响条数为零”,则回滚事务

}

 

SmartPersistenceLayer的使用:

1.               如果要对某个表进行并发控制,则给此表定义一个字符型的字段,由于采用Ticks的值,所以字符长度建议20个字符以上,比如叫TS

2.               ClassMap.xml中的字段属性上添加“timestamp="true"”,以此标明是“时间戳”字段,如:
<attribute name="ts" column="ts" type="String" timestamp="true"/>

3.               在实体Save()时,用“影响条数”来判断是否存在并发操作:

StudentEntity Student=new StudentEntity(); 

Student.Id
=1

Student.Retrieve(); 

If(Student.IsPersistent) 



   Student.Name
=… 

  ….             
//属性赋值,不需要给TS赋值 

Try 



If(Student.Save()
>0


     
//操作成功 

}
else 



     
//存在并发错误,提示用户重新操作。 

}
 

}
catch 



     
//保存数据发生异常 

}
 

}
 

 

 

 

 

 

从上面的代码可以看出,遇到保存异常与并发操作是两回事,在我们进行“更新”都是建议进行一次Retrieve()操作的。这样可以减少发生并发错误的可能性。

其他的Delete()同上面的类似,也采用If(Student.Delete()>0)来进行判断,这也可以方便显示“影响条数”:
int affect=Student.Delete(); //affect
为删除的记录数

 

这是对于单个实体进行的并发控制,由于目前无法进行“批量更新”的并发控制,所以没有对UpdateCriteria进行并发控制,讨论文章:再谈数据的并发处理

 

事务中对并发的控制

SPL中,对事务也进行了并发控制,事务中定义了一个属性IsForceCommit:是否强行执行。

 如果IsForceCommit=false(此为默认值),则在事务中,如果遇到并发错误,整个事务都将Roolback

 如果IsForceCommit=true,则在事务中,就算遇到并发错误,也会Commit此事务

这里所说的并发错误,其实就是指Update()Delete()是否影响条数。

Transaction t=New Transaction(); 

t.IsForceCommit
=true;            //可选,默认为false 

t.AddSaveObject(Student); 

t.AddSaveObject(Student2); 

…. 

t.Process();             
//根据IsForceCommit的值进行事务提交 

 

到此为止,在SPL可以轻松实现分布式异构数据库的事务提交,这可以对SPL支持数据库的任意组合提供并发处理。

 

PS:SPL3.1版将不久发布。

                                                   听棠

                                                 2005-4-4

Feedback

#1楼  回复 引用   

2005-04-04 14:48 by sunrise
非常好。另外我想
Transaction
由我自己决定是不是要commit,如何处理?

#2楼[楼主]  回复 引用 查看   

2005-04-04 15:02 by 听棠.NET      
你就是给t.IsForceCommit赋值啊。
默认是false,如果你想要强行执行,那就赋为t.IsForceCommit=true即可。

#3楼  回复 引用   

2005-04-04 19:30 by 极速麻醉
支持大哥!

#4楼  回复 引用 查看   

2005-04-04 19:51 by 天生这样      
我在使用中发现t.IsForceCommit设置为TRUE或是FALSE后都会使Transaction 失败,但如果没有这句,就是成功的,我在插入数据的时候发现有这样的问题

#5楼[楼主]  回复 引用 查看   

2005-04-04 20:11 by 听棠.NET      
呵:)我的这个新版本还没有发布出来呢。你的当然不行了。
如果有人急着想用的话,我可以发mail给他。

#6楼  回复 引用   

2005-04-04 20:23 by q.yuhen
提个小建议,建议能自动创建表。这样对于某些小型系统的开发就不需要同时维护数据库、代码和XML配置文件了。XPO.net的Class-DB还是很方便的。

另外,什么时候支持关系和继承啊?

当然,您的SPL非常棒。祝好!

#7楼[楼主]  回复 引用 查看   

2005-04-04 21:17 by 听棠.NET      
 呵,自动创建表,太麻烦了吧。而且不同的数据库创建表都是不一样的。不会搞的跟PD,ERwin一样吧:)
 目前,在SPL中也很容易实现“关系”的功能。所以目前还是没有追加“关系”的打算。
 比如订单与订单明细(假设OrderId为已知的订单Id):
 OrderHeaderEntity oh=new OrderHeaderEntity();
 oh.Id
=OrderId;
 oh.Retrieve();
 
if(oh.IsPersistent)
{
    RetrieveCriteria rc
=new RetrieveCriteria(typeof(OrderDetail));
    rc.GetNewCondition().AddEqualTo(OrderDetail.__OrderHeadId,OrderId); 
   DataTable dt
=rc.AsDataTable(); //这里可以使用多种返回方式,比如EntityContainer,Cursor等都可以,用DataTable返回方便Grid的绑定
}

 我目前觉得这样的RetrieveCritera也是比较方便的,不需要为了“关系”配置那些XML。而且返回方式比较灵活,再者可以添加多种查询条件,指定排序等,控制更自由。
 当然,以后考虑到ORM的整体思想,会加上去的。不过,目前的SPL还是强调易用性为主。

#8楼  回复 引用   

2005-04-05 09:18 by baogong
支持,好用的才是最好的。

#9楼  回复 引用 查看   

2005-04-05 09:46 by Austin leng      
不错,
上一个版本我还没弄清楚呢,先搞懂前一个版本再说

#10楼  回复 引用   

2005-04-05 15:21 by sunrise
你就是给t.IsForceCommit赋值啊。
默认是false,如果你想要强行执行,那就赋为t.IsForceCommit=true即可。
不是这个意思。

#11楼  回复 引用   

2005-04-05 18:01 by grape88
可否考虑发行收费版本? 现在的版本既不收费,也不开源。不敢在商业开发中用啊,没有任何保障啊! 只能停留在玩一玩的地步。

#12楼[楼主]  回复 引用 查看   

2005-04-07 09:22 by 听棠.NET      
@grape88 :
收费版本也可以的,我们提供一定的保障与维护,你可以试用一段时间,如果想正式采用到项目中,我也是建议收费给你们一个保障。如果你有意向,可以联系我:tintown_liu@hotmail.com

#13楼  回复 引用   

2005-04-11 15:20 by 笨笨
t.IsForceCommit只是处理了一种情况,典型的一棍子打死。
如果一个事物涉及到n(n>3个操作),只有其中一个是我希望出错也能够强行commit的,自然会IsForceCommit=true,但是结果就是该事物后面的操作出了任何意外都会无条件提交。粗粗看了一遍,不知道说得对不对?

#14楼[楼主]  回复 引用 查看   

2005-04-11 16:35 by 听棠.NET      
@笨笨 :
你说的很对,现在的IsForceCommit是针对整个事务的,我想,在大部分的情况下,这已经可以满足了。
从理论上讲,确实这不够好,如果要实现最好的效果,把IsForceCommit分配到每一个操作上。
以后考虑!!

#15楼  回复 引用   

2005-07-29 00:45 by Cataero[未注册用户]
关于并发控制的一个建议:
update sometable set somecol=somevalue,ver=ver+1 where key={key} and ver={ver}

这个是否比tick更合适呢?

#16楼  回复 引用   

2009-06-08 17:00 by 师出无名[未注册用户]
老大你的并发处理不行啊。不如专门针对SQL写一个就行了。以前用挑不出毛病。今天用到时间戳这里。实在接受不了了。