【问题标题】:What is the practical use of timestamp column in sql server with example?sql server中时间戳列的实际用途是什么?
【发布时间】:2013-07-20 01:38:29
【问题描述】:

我在sql server中遇到了timestamp数据类型,sql server中timestamp列的实际用途是什么?

【问题讨论】:

    标签: sql-server


    【解决方案1】:

    让我们以销售订单表为例来说明时间戳的作用。

    create table saleorder (ordernumber int, amount int, timestamp);
    insert into saleorder (ordernumber, amount) values (1, 100), (2, 100), (3, 200);
    select * from saleorder
    

    注意时间戳列中的数据。 Documentation of timestamp(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 是最近更新的。但是,更重要的是,timestamp 的值是在并发写入发生时出现的。

    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 数据类型,它与时间戳数据类型同义。 Documentation of rowversion (SQL Server 2012) 有一些有趣的例子。

    【讨论】:

    • 第二个输出列表中 ordernumber 1 的金额值需要更改为 200。
    • @ulty4life - 敏锐的眼睛!我已经进行了更新。感谢您的改进。
    • 为什么 DateTime 不是驱动此警报的合适机制? (不是一个修辞问题——真正的问题,因为我是新来的时间戳)。 +1。
    • 这是个好问题!如果并发处理得当,日期时间和递增版本号也是驱动此警报的好方法。有关更多讨论,请参阅此答案:stackoverflow.com/questions/7105093/…
    【解决方案2】:

    我使用时间戳列来跟踪数据何时发生变化,特别是需要同步到一个或多个移动应用程序的数据。您可以使用时间戳列仅返回自某个时间点以来已更改的行(通过提供上一个时间戳)。

    【讨论】:

      【解决方案3】:

      我使用TIMESTAMP数据类型(ROWVERSION,SQL2005+)来避免lost update problem

      丢失更新问题:第二个事务写入第二个值 在第一个写入的第一个值之上的数据项(数据) 并发事务,第一个值丢失给其他 并发运行的事务,根据它们的优先级,需要 读取第一个值。读取错误值的事务 以不正确的结果结束。

      示例: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**.
      

      示例:How to prevent the lost update 的 T-SQL 脚本(警告:您必须使用两个 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);

      【讨论】:

      • 为了避免丢失更新问题,修改日期不会少吗?
      • @Imad:这可能是一个解决方案,但开发人员应该小心更新此列 ([Modified Date])。 timestamp / rowversion 列的大 + 由 SQL Server 数据库引擎在连接执行 UPDATE 语句时自动更新
      • 这也称为乐观并发锁定。这与另一个正确的解决方案相反:a)begin transaction b)select with repeatable read 以确保没有人可以更改金额 c)update amount d)commit transaction。 SQL Server 有一个非常强大的锁定系统,人们可以完全按照它们的用途来利用这些功能。
      猜你喜欢
      • 2011-04-02
      • 2019-05-17
      • 2015-04-22
      • 2021-07-22
      • 1970-01-01
      • 2017-01-02
      • 1970-01-01
      • 2011-09-20
      相关资源
      最近更新 更多