确定自SQL Server上次访问以来的行更改

时间:2010-09-28 04:46:38

标签: sql sql-server database-design concurrency

我们有一个多用户系统,用户可以保存到中央SQL Server 2005数据库。我们遇到了一个问题,即用户刷新db中的更改而另一个用户保存新数据。我们当前收集更改的方式是每个表上都有一个时间戳列,每列都插入/更新。另一个用户将在客户端上存储时间戳,这是他最后一次从数据库中取出时间。

每次保存都在交易中完成。我们正在处理的例子如下:

  • User1启动保存,打开事务并插入/修改行,更改其时间戳。
  • User1在提交更改之前从数据库刷新,以某种方式导致User2的时间戳更新。
  • 用户1提交交易和所有更改。
  • User2再次从数据库刷新,但是因为他的时间戳先前已更新,只有User1提交的更改的后半部分被拉入并导致错误和应用程序崩溃。

这使我们认为时间戳不一定是用于确定自前端系统上次访问以来数据库更改的最佳方法。什么是更好的解决方案?

更多示例

  • User1启动保存,打开事务并插入/修改行并更新其时间戳。
  • User2启动另一次保存,打开一个事务,插入/修改更新其时间戳的OTHER行,并提交他的交易。
  • User3从数据库刷新并下拉User2提交的所有数据,将其LastRefreshTimestamp更新为User2在db中创建的最后一个时间戳。
  • User1提交他的交易。
  • 用户3再次从数据库刷新,但是在User2的事务结束和User1的事务结束之间根据其LastRefreshTimestamp撤消所有更改,在User2的事务开始之前错过了User1的事务所提交的所有更改。

8 个答案:

答案 0 :(得分:8)

有趣的问题,我想不出一个简单干净的基于T-SQL的解决方案,但这正是SQL 2008中的Change Tracking创建的同步挑战类型...... http://msdn.microsoft.com/en-us/library/bb933875.aspx < / p>

本博客/文章中对变更跟踪与变更数据捕获的非常高级概述:http://blogs.technet.com/b/josebda/archive/2009/03/24/sql-server-2008-change-tracking-ct-and-change-data-capture-cdc.aspx

如果您的一般目标是保留存储库的客户端副本,则可以将此与Microsoft Sync Framework结合使用:http://msdn.microsoft.com/en-us/sync/bb887608

答案 1 :(得分:2)

我想我理解你的问题:

每个客户端都维护一个断开连接的数据集,并定期刷新其数据集的本地副本。

您使用SQL-92时间戳(与SQL Server时间戳不同;一个是日期时间,一个是二进制行版本)来跟踪更新,以便您可以在数据集中找到增量并更新客户端。

这种方法导致问题,因为您的时间戳是在事务完全提交之前计算的,后续事务计算更新的时间戳,但可能在旧事务之前提交,并且您的“最新时间戳”错过了部分或全部记录。

那可以做些什么?

事实上,这是一个“难以破解的难题”,这是一个很好的迹象,表明这是一种非典型的设计模式,尽管我认为改变这种模式是不可能的。

以下是一些可行的解决方案。

  1. 在您的刷新中构建“错误边际”。如果您的上一次刷新是2011-08-06 23:14:05减去几分钟(您将不得不弄清楚该误差范围是多少)并获得这些更新。这将是您的快速修复创可贴解决方案。如果要优化此解决方案,请使用SQL Server时间戳(自动二进制行版本)计算所有行的校验和,并将其存储在本地数据集中,并在刷新期间比较行。

  2. 懒惰的刷新。同样,使用SQL Server时间戳(rowversion)进行行版本控制并在允许用户编辑之前检查更改,如果时间戳不匹配则执行刷新。检查时,使用NOWAIT提示确定当前是否正在编辑该行。通常,您在保存时会再次执行此检查以确保没有冲突(http://www.mssqltips.com/tip.asp?tip=1501)这是一种更典型的方法。

  3. 最终,您不需要在客户端上维护整个数据集。 SQL Server在处理并发和多个用户时非常好。如果必须对本地数据进行搜索,则最好在服务器上执行这些搜索。如果您遇到阻止问题,最好还是专注于尝试缩短交易时长。一般来说,交易等待用户输入是一个糟糕的想法。你的问题表明可能正在发生,但我不想做任何事情。

    除此之外,想法/选项变得越来越糟/疯狂(例如,您可以在客户端上放置SQL(Express?)实例,并使用复制推送更改并将这些实例用于本地数据存储。但复制延迟可以在那里是一个主要问题,我相信这会导致比它解决的更多问题。)

    还有另一种可能性

    我对你的问题的解释可能是错的。您可能正在计算客户端上的更新时间戳,然后将其保存到服务器。如果是这种情况,则不要将明确的日期时间传递给服务器,而是传入GETDATE()。这将计算动态的日期时间。

    如果有太多代码需要重构,你也可以使用触发器处理它:

    简单示例:

    CREATE TRIGGER Trig_MyTable_Update ON dbo.MyTable FOR INSERT, UPDATE
    AS 
    IF @@ROWCOUNT=0 RETURN 
    UPDATE U SET UpdateDT = GETDATE()
    FROM MyTable U INNER JOIN INSERTED I ON U.Id = I.Id
    

答案 2 :(得分:2)

我假设时间戳是这里的rowversion二进制(8)类型。由于任何旧的原因,这会增加:例如,如果您执行update-.. set col1 = col1..

我们发现太多依赖时间戳的误报。

让我们说User5加载数据,但User4更改然后恢复(毕竟,他们是最终用户......),您将有2个时间戳更改但相同的基础数据。

基于日期时间的真实系统,即使精确到1秒或更小,最终也会在高负载下失败,但您遇到同样的问题。

我们通过

解决了这个问题
  • 使用时间戳(它们以虚拟更新递增)
  • 使用REPEATABLE_READ隔离
  • 每个保存也会发送旧值
  • 比较要更新的行的每个值

因此User3加载在服务器上更改的一组数据。保存时,值的任何更改都会引发错误。

如果您可以禁止没有实际更改数据的UPDATE,那么时间戳不会触发,那么您就不需要了。

如果您愿意,可以添加追踪虚拟更新的额外列,即使它不是“真正的”更新,也会确认用户“保存”..但这会改变行反转值

因此我们的解决方案......

答案 3 :(得分:1)

为提交之前创建/更新的任何行添加时间戳的更新。

无论如何,用户端检查不会取代服务器端检查(和约束),所以这种机制只是为了用户的舒适,而不是数据验证的最后方法......

答案 4 :(得分:1)

将客户端的时间戳设置为上次拉取的日期/时间,并结合服务器端事务期间的时间戳,是您的问题所在。最后一次“更新/影响所有受影响记录的时间戳”作为事务中的最后一个操作 - 尽管您可能仍会遇到时间戳解决问题,或者更改拉动逻辑以根据客户端之间的时间戳差异选择记录和服务器,而不是将所有记录时间戳与单个“拉”日期/时间进行比较。

答案 5 :(得分:1)

查看AFTER触发器http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/triggers.htm#CNCPT017,数据库触发器是一种特殊类型的存储过程,在发生预定义事件时会自动调用,例如用户保存新数据。

USE database
GO
CREATE TRIGGER on_table_update_insert_delete
ON table AFTER update, delete, insert
AS
DECLARE @string AS char(1)

IF EXISTS(SELECT * FROM inserted)
    IF EXISTS(SELECT * FROM deleted)
    SET @string = 'U';
    ELSE
    SET @string = 'I';
    ELSE
    SET @string = 'D'
INSERT INTO event_logs(username, event_type, event_time, table_name)
SELECT SYSTEM_USER @string, getdate(), 'Table'
GO

要启用触发器,您需要输入此代码

USE database
GO
ENABLE TRIGGER on_table_update_insert_delete ON DATABASE
GO

谢谢:)

答案 6 :(得分:0)

在交互式系统中,带有一秒钟解析的时间戳通常就足够了。更高分辨率的时间戳可以降低更新失败的风险。失败通常指向非原子更新,或针对同一请求涉及针对相同数据的多个事务的情况。

您的问题看起来像非原子更新,或隔离级别,允许看到未提交的更改。在任何情况下,应用程序都应该处理数据更新而不会崩溃。通常,应该显示由另一个用户错误更新的某种数据。

要考虑更新的数据的最新时间戳可以与应用程序一起使用,以确保数据在显示后但在更新之前不会被修改。批量更新可能会导致时间戳问题,因为它们可以非常快速地完成。

可以使用事务计数器,但需要跟踪每条记录的计数器。

答案 7 :(得分:-1)

timestamp类型在某些sql实现中遇到可能在同一“秒”期间发生多个事务的情况。对于具有高分辨率计时器(例如纳秒)的sql实现,它不太可能成为问题。

相反,如何在某处保留交易柜台?每次更新完成后,请将其递增。