我有一个像 client / server / db 这样的架构的应用程序。客户端和服务器之间的通信是WCF(已从asmx迁移),数据库是SQL Server 2005。
项目有一项要求,即您无法更新在初次阅读后由其他用户更改(更新)的订单。我认为在大多数应用程序中都有一个共同的要求。
订单的更新通常为:
这种处理数据更改的方法有效,在某一点(3),服务器将在内存中有3个(不同的)订单副本!有人知道另一种策略吗?
我们正在使用AspNetBackwardCompability运行WCF,因为我们需要Session-variable来“保存”最初读取的副本 - 如果我们可以转储那么它将成为我的一天
答案 0 :(得分:2)
一种解决方案是让客户端在保存时提供初始读取的值和更新的值。然后,您不需要会话中原始值的副本。
DataSet具有存储两个版本(DataRowVersion.Original和DataRowVersion.Current)的内置功能,但您必须提供自己的方法来执行此操作(例如,operationContract:
SaveMyData(MyType original, MyType updated);
然后您可以保存到数据库:
UPDATE MyTable
SET Col1 = @NewCol1, Col2 = @NewCol2, ...
WHERE Col1 = @OldCol1, Col2 = @OldCol2, ...
IF @@ROWCOUNT = 0 ... update failed ...
或者,您可以在表中使用TIMESTAMP / ROWVERSION列。您将此往返到客户端,并在更新时进行测试:
UPDATE MyTable
SET Col1 = @NewCol1, Col2 = @NewCol2, ...
WHERE PKCol = @PK AND TimeStampCol = @OldTimeStamp
IF @@ROWCOUNT = 0 ... update failed ...
您当然依赖客户端在保存时正确返回原始值/原始时间戳。但这不是一个安全问题 - 恶意客户端不会对基于会话的解决方案造成任何损害。
答案 1 :(得分:1)
什么是防止在4之后3?之前发生并发更改?
处理这个的常用方法是消除步骤3(除非在可重复的读隔离级别中完成,否则是不正确的,这完全是过度杀伤)并乐观地应用变化跳跃没有任何改变(即乐观并发模型) )。要强制执行确实没有任何更改,您可以使用包含 all 旧值的WHERE子句,或者添加到每次更新时更改的WHERE子句特殊列,例如时间戳或行版本。
如果更新是no-op(它没有找到旧的值,使用各种方式检查,比如检查@@ ROWCOUNT或使用OUTPUT子句),那么你可以阅读new,modified,values并通知客户端,仅限于例外情况。
答案 2 :(得分:1)
您已实施的有状态服务是一项大型服务反模式。 Web服务应该是无状态的一般原则,否则您的可伸缩性可能会受到影响。对于乐观锁定,请使用表的时间戳列。将此作为并发令牌返回给客户端,该客户端将保持不变,并在更新前与DB中的值进行比较。我在sql server上是原始的,但是oracle已经选择了可以帮助你的更新语句。
如果数据分布在许多表中,请考虑使用存储过程的合适锁定策略。
答案 3 :(得分:0)
我喜欢乔的方法 - 这也是我推荐的方式。
在您的初始读取中,将主表中的TIMESTAMP
列的值发送回客户端,可以是实际的DataContract,也可以是WCF响应消息中的标头。
如果要更新数据,请将该初始时间戳值从客户端发送回服务器。然后,服务器将首先检查该时间戳值是否已更改,如果是,则抛出FaultException而不更新数据。仅当时间戳值仍然与客户端在UPDATE调用中返回的值相同时,服务器才会实际执行更新。
我建议使用SQL Server TIMESTAMP
数据时间(这不是与日期和/或时间有关的任何事情 - 它只是一个独特的,不断增加的数字,真的),因为那是一个比DATETIME准确得多,并且每次写入表中的行时SQL Server都会自动更新它。为检查更新提供了完美的“标记”。
使用这种方法,您需要传递的只是一个8字节的时间戳值 - 无需拥有整个数据行的三个精确副本。
请参阅此精彩文章“Understanding TIMESTAMP (ROWVERSION) in SQL Server”。
马克