我在sql server中遇到了timestamp数据类型。sql server中timestamp列的实际用途是什么?
3 回答
我使用TIMESTAMP
数据类型(ROWVERSION
,SQL2005+)来避免丢失更新问题:
丢失更新问题:第二个事务在第一个并发事务写入的第一个值之上写入数据项(数据)的第二个值,并且第一个值丢失到其他并发运行的事务中,这些事务需要,按它们的优先级, 读取第一个值。读取错误值的事务以不正确的结果结束。
示例lost update
::
t : User 1 read payment order (PO) #1 (amount 1000)
t+1: User 2 read payment order (PO) #1 (amount 1000)
t+2: User 1 change the amount for PO #1 to 1005
t+3: User 2 change the amount for PO #1 to 1009 (change make by User 1 is lost because is overwritten by change make by User 2)
t+4: The amount is **1009**.
示例:如何防止lost update
:
t : User 1 read payment order (PO) #1 (amount 1000, timestamp 0x00000000000007D1)
t+1: User 2 read payment order (PO) #1 (amount 1000, timestamp 0x00000000000007D1)
t+2: User 1 change the amount for PO #1 to 1005 and it checks if row has the same `timestamp` (column `RW` in this case; 0x00000000000007D1). The check succeeds and the change is `COMMIT`ed. This will change, also, the timestamp (column 'RW'). The new timestamp is 0x00000000000007D4.
t+3: User 2 change the amount for PO #1 to 1009 and it checks if row has the same `timestamp` (column `RW` in this case; 0x00000000000007D4). The checks fails because the initial timestamp (@rw=0x00000000000007D1) is <> than current timestamp (column `RW`=0x00000000000007D4). An error is raised the catch block "intercepts" the error and this transaction is cancelled (`ROLLBACK`).
t+4: The amount {remains|is} **1005**.
示例:T-SQL 脚本How to prevent the lost update
(警告:您必须使用两个 SSMS 窗口/两个会话)
CREATE DATABASE TestRowVersion;
GO
USE TestRowVersion;
GO
CREATE TABLE dbo.PaymentOrder(
PaymentOrderID INT IDENTITY(1,1) PRIMARY KEY,
PaymentOrderDate DATE NOT NULL,
Amount NUMERIC(18,2) NOT NULL,
CreateDate DATETIME NOT NULL DEFAULT (GETDATE()),
UpdateDate DATETIME NULL,
RW ROWVERSION NOT NULL -- R[ow] V[ersion]
);
GO
INSERT dbo.PaymentOrder (PaymentOrderDate,Amount)
VALUES ('2013-07-21',1000);
INSERT dbo.PaymentOrder (PaymentOrderDate,Amount)
VALUES ('2013-07-22',2000);
INSERT dbo.PaymentOrder (PaymentOrderDate,Amount)
VALUES ('2013-07-23',3000);
GO
SELECT * FROM dbo.PaymentOrder;
/*
PaymentOrderID PaymentOrderDate Amount CreateDate UpdateDate RW
-------------- ---------------- ------- ----------------------- ---------- ------------------
1 2013-07-21 1000.00 2013-07-21 09:35:38.750 NULL 0x00000000000007D1
2 2013-07-22 2000.00 2013-07-21 09:35:38.750 NULL 0x00000000000007D2
3 2013-07-23 3000.00 2013-07-21 09:35:38.750 NULL 0x00000000000007D3
*/
GO
-- User 1 (SQL Server Management Studio/SSMS window #1)
-- [t] Client app, user 1: it loads first PO
SET NOCOUNT ON;
GO
DECLARE @PaymentOrderID INT=1; -- parameter
SELECT po.PaymentOrderID,
po.PaymentOrderDate,
po.Amount,
po.RW
FROM dbo.PaymentOrder po
WHERE po.PaymentOrderID=@PaymentOrderID;
-- Client app, user 1: during 15 seconds it edit the amount from 1000.00 to 1005.00
WAITFOR DELAY '00:00:15';
GO
-- [t+2] Client app, user 1: it sends this change (new amount) from client app to database server
-- with the old row version value
DECLARE @PaymentOrderID INT=1; -- parameter
DECLARE @rw BINARY(8)=0x00000000000007D1; -- parameter
DECLARE @NewAmount NUMERIC(18,2)=1005.00; -- parameter
BEGIN TRY
BEGIN TRANSACTION
UPDATE dbo.PaymentOrder
SET Amount=@NewAmount
WHERE PaymentOrderID=@PaymentOrderID
AND RW=@rw; -- it checks the timestamp (current timestamp versus original timestamp)
DECLARE @rowcount INT=@@ROWCOUNT; -- How many rows were affected by the last statement (UPDATE in this case) ?
SELECT @rowcount AS [@@ROWCOUNT];
IF @rowcount<>1
RAISERROR('Lost update or row deleted.', 16, 1);
COMMIT TRANSACTION
PRINT 'UPDATE succeded';
END TRY
BEGIN CATCH
IF @@TRANCOUNT>0
ROLLBACK;
DECLARE @ErrMsg NVARCHAR(2002);
SET @ErrMsg=ERROR_MESSAGE();
RAISERROR(@ErrMsg,16,1);
END CATCH;
GO
-- [t+4] Client app, user 1: it reloads first PO
DECLARE @PaymentOrderID INT=1; -- parameter
SELECT po.PaymentOrderID,
po.PaymentOrderDate,
po.Amount,
po.RW
FROM dbo.PaymentOrder po
WHERE po.PaymentOrderID=@PaymentOrderID;
GO
-- User 2 (warning: run this script in another SQL Server Management Studio window: File > New Database Engine Query !; SSMS window #2)
-- [t+1] Client app, user 1: it loads first PO
SET NOCOUNT ON;
GO
DECLARE @PaymentOrderID INT=1; -- parameter
SELECT po.PaymentOrderID,
po.PaymentOrderDate,
po.Amount,
po.RW
FROM dbo.PaymentOrder po
WHERE po.PaymentOrderID=@PaymentOrderID;
-- Client app, user 1: during 20 seconds it edit the amount from 1000.00 to 1005.00
WAITFOR DELAY '00:00:20';
GO
-- [t+4] Client app, user 1: it sends this change (new amout) from client app to database server
-- with the old row version value
DECLARE @PaymentOrderID INT=1; -- parameter
DECLARE @rw BINARY(8)=0x00000000000007D1; -- parameter
DECLARE @NewAmount NUMERIC(18,2)=1009.00; -- parameter
BEGIN TRY
BEGIN TRANSACTION
UPDATE dbo.PaymentOrder
SET Amount=@NewAmount
WHERE PaymentOrderID=@PaymentOrderID
AND RW=@rw; -- it checks the timestamp (current timestamp versus original timestamp)
DECLARE @rowcount INT=@@ROWCOUNT; -- How many rows were affected by the last statement (UPDATE in this case) ?
SELECT @rowcount AS [@@ROWCOUNT];
IF @rowcount<>1
RAISERROR('Lost update or row deleted.', 16, 1);
COMMIT TRANSACTION
PRINT 'UPDATE succeded';
END TRY
BEGIN CATCH
IF @@TRANCOUNT>0
ROLLBACK;
DECLARE @ErrMsg NVARCHAR(2002);
SET @ErrMsg=ERROR_MESSAGE();
RAISERROR(@ErrMsg,16,1);
END CATCH;
GO
-- [t+4] Client app, user 1: it reloads first PO
DECLARE @PaymentOrderID INT=1; -- parameter
SELECT po.PaymentOrderID,
po.PaymentOrderDate,
po.Amount,
po.RW
FROM dbo.PaymentOrder po
WHERE po.PaymentOrderID=@PaymentOrderID;
GO
用户 1 的结果(金额 1000 -> 1005):
PaymentOrderID PaymentOrderDate Amount RW
-------------- ---------------- --------------------------------------- ------------------
1 2013-07-21 1000.00 0x00000000000007D1
@@ROWCOUNT <- Timestamp check succeeds
-----------
1
UPDATE succeded
PaymentOrderID PaymentOrderDate Amount RW
-------------- ---------------- --------------------------------------- ------------------
1 2013-07-21 1005.00 0x00000000000007D4
用户 2 的结果(金额 1000 -> 1009):
PaymentOrderID PaymentOrderDate Amount RW
-------------- ---------------- --------------------------------------- ------------------
1 2013-07-21 1000.00 0x00000000000007D1
@@ROWCOUNT <- Timestamp check fails
-----------
0
Msg 50000, Level 16, State 1, Line 27
Lost update.
PaymentOrderID PaymentOrderDate Amount RW
-------------- ---------------- --------------------------------------- ------------------
1 2013-07-21 1005.00 0x00000000000007D4
注意:将错误消息更改为RAISERROR('Lost update or row deleted.', 16, 1);
让我们以销售订单表为例来说明时间戳的作用。
create table saleorder (ordernumber int, amount int, timestamp);
insert into saleorder (ordernumber, amount) values (1, 100), (2, 100), (3, 200);
select * from saleorder
注意时间戳列中的数据。时间戳文档(SQL Server 2005)说:这(即时间戳)跟踪数据库中的相对时间,而不是可以与时钟关联的实际时间......每次修改或插入带有时间戳列的行,增加的数据库时间戳值被插入到时间戳列中。
让我们看看数据是什么样子的:
ordernumber amount timestamp
1 100 0x00000000000007D1
2 100 0x00000000000007D2
3 200 0x00000000000007D3
好吧。首先添加订单 1,最后输入订单 3。如果我们要更新订单 1 的数量会发生什么?
update saleorder set amount = 200 where ordernumber = 1
select * from saleorder
啊,请注意订单 1 的时间戳现在是 0x7D4(2004 年十进制)。关于其他行,我们知道订单 1 是最近更新的。但是,更重要的是,时间戳的价值是在并发写入发生时出现的。
ordernumber amount timestamp
1 200 0x00000000000007D4
2 100 0x00000000000007D2
3 200 0x00000000000007D3
假设 John 和 Mary 都在使用以 .NET 开发的 Web 应用程序处理订单 3。约翰调出订单并进行更改。John 尚未保存数据。玛丽拉了同样的订单并改变了它。约翰先救人。Mary 尝试保存数据。.NET 应用程序可以首先查看 Mary 提取的时间戳是否仍然与数据库中订单 3 的时间戳相同。
如果 Mary 使用订单 3 提取的时间戳现在不同(因为 John 保存了数据并且时间戳自动更改),.NET 应用程序可以提醒 Mary 并要求她刷新屏幕上的记录以查看最新更改(或者可能突出显示在屏幕上更改)。
将时间戳视为行版本。有趣的是,SQL Server 的最新版本使用 rowversion 数据类型,它与时间戳数据类型同义。rowversion (SQL Server 2012) 的文档有一些有趣的例子。
我使用时间戳列来跟踪数据何时发生变化,特别是需要同步到一个或多个移动应用程序的数据。您可以使用时间戳列仅返回自某个时间点以来已更改的行(通过提供上一个时间戳)。