2010-09-28 34 views
4

我們有一個多用戶系統,用戶將用戶保存到中央SQL Server 2005數據庫。我們遇到了一個問題,即用戶刷新數據庫中的更改,而另一用戶保存新數據。我們目前正在收集這些更改的方式是每個表的每個行的插入/更新時都會有一個時間戳列。另一個用戶將有一個時間戳存儲在客戶端,這是他從數據庫中最後一次拉出來的時間。確定自SQL Server上次訪問以來的行更改

每次保存都是在一個事務中完成的。我們正在處理的示例如下:

  • 用戶1啓動保存,打開事務並插入/修改行,更改其時間戳。
  • 用戶2在數據庫刷新之前User1已經提交了更改,以某種方式導致User2的時間戳更新。
  • 用戶1提交交易和所有更改。
  • User2從數據庫中再次刷新,但是因爲他的時間戳已更新,只有User1提交的更改的後半部分導致錯誤和應用程序崩潰。

這使我們認爲時間戳不一定是用於確定自前端系統上次訪問以來數據庫更改的最佳方法。什麼是更好的解決方案?

此外例如

  • User1開始時保存,打開事務和插入/修改的行和更新其時間戳。
  • User2啓動另一個保存,打開一個事務,插入/修改更新其時間戳的OTHER行,並提交他的事務。
  • User3從數據庫刷新並拉下User2提交的所有數據,將其LastRefreshTimestamp更新爲由User2在db中創建的最後一個時間戳。
  • User1提交他的交易。
  • 用戶3再次從數據庫中刷新,但是在User2的事務結束和User1的事務結束之間根據LastRefreshTimestamp提取所有更改,並在User2事務開始之前忽略User1事務提交的所有更改。
+3

SELECTing數據如何更改時間戳列?或者你是否也在搞隔離級別? – 2010-09-28 04:56:51

+1

我假設你指的是「User2在用戶提交更改之前從數據庫刷新,以某種方式導致User2的時間戳更新。」它不是一個改變時間戳的選擇,很可能發生了什麼是某人正在提交導致時間戳更新的其他更改。 – link664 2010-09-28 05:10:14

+0

當User1保存到數據庫時,實際發生了什麼變化?你能提供更新Users表的SQL嗎?此外,用戶表中是否有觸發器(或更新用戶表)? – jveazey 2011-08-05 03:28:46

回答

8

有趣的問題扳機,我想不出一個簡單幹淨的T-SQL-的但是這正是同步挑戰的類型,即SQL 2008中的變更跟蹤是爲創建... http://msdn.microsoft.com/en-us/library/bb933875.aspx

很好的高級別更改跟蹤概述g與變化數據捕捉這個博客/文章:http://blogs.technet.com/b/josebda/archive/2009/03/24/sql-server-2008-change-tracking-ct-and-change-data-capture-cdc.aspx

而且你有可能與微軟同步框架結合這一點,如果你的總體目標是保持庫中的客戶端的副本:http://msdn.microsoft.com/en-us/sync/bb887608

+0

很好的回答:) – Killrawr 2011-08-23 08:39:58

-1

timestamp類型在某些sql實現中遇到潛在的多個事務在同一「秒」期間發生時會受到影響。對於具有高分辨率計時器的SQL實現(例如,納秒),這不太可能成爲問題。

取而代之的是,如何保持一個交易櫃檯?每次更新完成後,增加它。

+0

有一個事務計數器的問題是兩個事務可以同時運行導致問題。我會用另一個例子更新簡報。 – link664 2010-09-28 05:13:19

+0

不在SQL Server中。 'timestamp'與時間無關,並且是用於行版本控制的計數器。 – 2010-09-29 08:49:42

0

在一個交互式系統中,時間戳的解決通常是足夠的。更高分辨率的時間戳可以降低更新失敗的風險。失敗通常指向非原子更新,或者針對同一請求的相同數據涉及多個事務的情況。

您的問題看起來像是非原子更新,或者是允許看到未提交更改的隔離級別。在任何情況下,應用程序都應該處理數據更新而不會崩潰。通常情況下,應提供某種由其他用戶錯誤更新的數據。

正在考慮更新的數據的最新時間戳可以與應用程序一起使用,以確保數據在顯示之後但未更新之前不會被修改。批量更新可能會導致時間戳問題,因爲它們可以非常迅速地完成。

可以使用交易計數器,但是需要跟蹤每個記錄的計數器。

+0

問題不在於更新衝突,它在提交鏈中以及用於比較數據庫更新的存儲時間戳更新。 – link664 2010-09-28 05:30:27

+0

如果您的交易持續幾秒鐘,則用戶2和3需要進行全面刷新。這也適用於記錄可以被刪除的情況。您可以考慮使用現在作爲時間戳,並確保所有讀取的記錄都較舊。任何更新都會導致時間戳等於或大於現在的時間戳。您需要使用一致的時間源。如果您涉及多個服務器,NTP會有很大的幫助。 – BillThor 2010-09-29 05:07:41

+1

在SQL Server中,'timestamp'與時間無關,並且是用於行版本控制的計數器。 – 2010-09-29 08:51:10

1

爲提交之前創建/更新的任何行添加更新時間戳。

反正用戶端檢查不更換服務器端檢查(和約束),所以這種機制只對用戶的舒適性,而不是數據驗證的最後平均...

1

設置客戶端上的時間戳與最後一次拉取的日期/時間相結合,並在服務器端的事務處理期間加上時間戳,這是問題發生的地方。最後一次「更新/影響所有受影響記錄的時間戳記」作爲交易中的最後一個操作 - 雖然您可能仍會遇到時間戳解析問題,或更改拉動邏輯以根據客戶端之間的時間戳差異選擇記錄和服務器,而不是將所有記錄時間戳與單個「拉」日期/時間進行比較。

+0

在SQL Server中,'timestamp'與時間無關,並且是用於行版本控制的計數器。 – 2010-09-29 08:54:02

+1

@Martin:那麼?時間戳和時間戳,因爲術語不僅限於你知道的sql時間戳列... – 2010-09-29 10:06:39

+0

@Marjan - 如果他們在他們的問題中說「我們在每個表上有一個時間戳列」,並且問題上的標記是'sql-服務器'我認爲這很清楚,他們意味着他們有一個[時間戳列](http://msdn.microsoft.com/en-us/library/aa260631%28SQL.80%29.aspx),他們沒有做任何事情與客戶端上的日期/時間。 – 2010-09-29 10:11:29

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 

謝謝:)

+1

我不確定這將如何幫助解決問題......當您參考Oracle文檔時,這是一個SQL Server問題,提供的觸發器仍然會在用戶範圍內執行事務(User1,在上面的例子中),因此記錄在日誌中的「event_time」仍然會比時間戳User3已經得到的更早...我不認爲這有效。 – Tao 2011-08-05 12:45:20

+0

上述代碼適用於SQL-T,我已經在使用Microsoft SQL Server Management Studio 2008 R2的MS SQL服務器上對其進行了測試。我爲oracle提供了一個URL,因爲他們有一些關於觸發器如何在數據庫中工作的信息,如果您有興趣的話。 – Killrawr 2011-08-07 00:35:40

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),這是更典型的方法。

最終,您不應該需要在客戶端上維護整個數據集。在處理併發和多個用戶時,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

我假定時間戳是rowversion二進制(8)在此輸入。由於任何舊的原因,這個增量:如果你做update-.. set col1 = col1..例如

我們發現太多依賴於時間戳的誤報。我們假設User5加載數據,但User4更改,然後恢復(畢竟,它們是最終用戶...),您將有2個時間戳更改,但具有相同的底層數據。

一個真正的基於日期時間的系統,即使精確到1秒或更小,最終會在高負載下失敗,但是您也有同樣的問題。

我們解決了這個利用時間戳(他們遞增與虛擬更新)

  • 使用REPEATABLE_READ隔離每個
  • 保存

    • 發送舊值過
    • 比較行的每個值待更新

    所以用戶3加載一組在服務器上更改的數據。當它們保存時,值的任何更改都會引發錯誤。

    如果您可以禁止更新沒有數據實際更改,以便時間戳不觸發那麼你不需要這個。

    ,如果你想使用戶「節省」是acknowleged即使它不是一個「真正」的更新。您可以添加額外的列跟蹤虛更新,但這ichanges一個rowversion值

    因此,我們以上解決方案...

  • 相關問題