假设,我即将使用ASP.NET和SQL Server 2005启动一个项目。我必须为此应用程序设计并发要求。我打算在每个表中添加一个TimeStamp列。在更新表时,我将检查TimeStamp列是否与选中时相同。
这种方法是否足够?或者在任何情况下这种方法都有缺点吗?
请建议。
由于
Lijo
答案 0 :(得分:4)
首先,您在问题中描述的方式在我看来是 最佳方式 ,适用于使用MS SQL作为数据库的ASP.NET应用程序。数据库中没有锁定。它非常适合与永久断开连接的客户一样的Web客户端。
如何从一些答案中读到,术语中存在误解。我们都意味着使用Microsoft SQL Server 2008或更高版本来保存数据库。如果您在MS SQL Server 2008文档中打开“rowversion(Transact-SQL)”主题,您将找到以下内容:
“时间戳是该词的同义词 rowversion 数据类型并受其约束 数据类型同义词的行为。“... “不推荐使用时间戳语法。 此功能将在a中删除 Microsoft SQL的未来版本 服务器。避免使用此功能 新的发展工作,并计划 修改当前使用的应用程序 这个功能。“
所以 timestamp 数据类型是MS SQL的 rowversion 数据类型的同义词。它保存64位计数器,该计数器存在于每个数据库的内部,可以看作是 @@ DBTS 。在数据库的一个表中修改一行后,计数器将递增。
在我阅读您的问题时,我将“TimeStamp”视为 rowversion 类型的列名称。我个人更喜欢名称 RowUpdateTimeStamp 。在AzManDB中(请参阅Microsoft授权管理器,将Store作为DB)我可以看到这样的名称。有时也使用 ChildUpdateTimeStamp 来跟踪分层 RowUpdateTimeStamp 结构(关于触发器)。
我在上一个项目中实施了这种方法并且非常高兴。通常你会这样做:
SELECT s.Id AS Id
,s.Name AS SoftwareName
,m.Name AS ManufacturerName
,CASE WHEN s.RowUpdateTimeStamp > m.RowUpdateTimeStamp
THEN s.RowUpdateTimeStamp
ELSE m.RowUpdateTimeStamp
END AS RowUpdateTimeStamp
FROM dbo.Software AS s
INNER JOIN dbo.Manufacturer AS m ON s.Manufacturer_Id=m.Id
或者像下面那样进行数据投射
SELECT s.Id AS Id
,s.Name AS SoftwareName
,m.Name AS ManufacturerName
,CASE WHEN s.RowUpdateTimeStamp > m.RowUpdateTimeStamp
THEN CAST(s.RowUpdateTimeStamp AS bigint)
ELSE CAST(m.RowUpdateTimeStamp AS bigint)
END AS RowUpdateTimeStamp
FROM dbo.Software AS s
INNER JOIN dbo.Manufacturer AS m ON s.Manufacturer_Id=m.Id
将 RowUpdateTimeStamp 保存为 bigint ,它对应于C#的 ulong 数据类型。如果从许多表中创建OUTER JOINT或JOINT,则会看到所有表中的构造MAX(RowUpdateTimeStamp)
稍微复杂一些。因为MS SQL不支持像MAX(a,b,c,d,e)这样的函数,所以相应的构造可能如下所示:
(SELECT MAX(rv)
FROM (SELECT table1.RowUpdateTimeStamp AS rv
UNION ALL SELECT table2.RowUpdateTimeStamp
UNION ALL SELECT table3.RowUpdateTimeStamp
UNION ALL SELECT table4.RowUpdateTimeStamp
UNION ALL SELECT table5.RowUpdateTimeStamp) AS maxrv) AS RowUpdateTimeStamp
spSoftwareUpdate
存储过程看起来像CREATE PROCEDURE dbo.spSoftwareUpdate
@Id int,
@SoftwareName varchar(100),
@originalRowUpdateTimeStamp bigint, -- used for optimistic concurrency mechanism
@NewRowUpdateTimeStamp bigint OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
-- ExecuteNonQuery() returns -1, but it is not an error
-- one should test @NewRowUpdateTimeStamp for DBNull
SET NOCOUNT ON;
UPDATE dbo.Software
SET Name = @SoftwareName
WHERE Id = @Id AND RowUpdateTimeStamp <= @originalRowUpdateTimeStamp
SET @NewRowUpdateTimeStamp = (SELECT RowUpdateTimeStamp
FROM dbo.Software
WHERE (@@ROWCOUNT > 0) AND (Id = @Id));
END
dbo.spSoftwareDelete
存储过程的代码看起来一样。如果您没有打开NOCOUNT
,则可以在场景中自动生成 DBConcurrencyException 。 Visual Studio为您提供了使用乐观并发的可能性,例如TableAdapter
或DataAdapter
的高级选项中的“使用乐观并发”复选框。
如果您查看dbo.spSoftwareUpdate
存储过程,您会发现,我在WHERE中使用RowUpdateTimeStamp <= @originalRowUpdateTimeStamp
而不是RowUpdateTimeStamp = @originalRowUpdateTimeStamp
。我这样做是因为,具有客户端的@originalRowUpdateTimeStamp
的值通常被构造为MAX(RowUpdateTimeStamp)
,而不是一个表。所以它可以是RowUpdateTimeStamp < @originalRowUpdateTimeStamp
。您应该使用严格相等 = 并在此处重现与SELECT语句中使用的相同的复杂JOIN语句,或者像我一样使用&lt; = 构造并保持完全相同的安全性之前。
顺便说一句,可以基于RowUpdateTimeStamp为 ETag 构建非常好的值,RowUpdateTimeStamp可以将HTTP头与数据一起发送到客户端。使用 ETag ,您可以在客户端实现智能数据缓存。
我不能在这里编写完整的代码,但你可以在Internet上找到很多例子。我想再重复一次,在我看来,使用基于 rowversion 的乐观并发性 是大多数ASP.NET场景的最佳方式
答案 1 :(得分:2)
在SQL Server中,针对情境类型的推荐方法是创建类型为'rowversion'的列,并使用该方法检查该行中的任何字段是否已更改。
SQL Server保证如果行中的任何值发生更改(或插入新行),它的rowversion列将自动更新为不同的值。让数据库为你处理这个比你自己做的更可靠。
在更新语句中,您只需添加一个where子句来检查rowversion值是否与第一次检索行时的值相同。如果没有,其他人改变了行(即:它很脏)
此外,从该页面开始:
不推荐使用时间戳语法。 此功能将在a中删除 Microsoft SQL的未来版本 服务器。避免使用此功能 新的发展工作,并计划 修改当前使用的应用程序 这个功能。
答案 2 :(得分:1)
我不确定应该像这样在数据库中处理并发性。数据库本身应该能够管理隔离和事务行为,但是线程行为应该在代码中完成。
答案 3 :(得分:0)
rowversion建议是正确的我会说,但令人失望的是看到时间戳很快就会被弃用。我的一些OLD应用程序使用它是出于不同的原因,然后检查并发性。