2009-11-30 73 views
6

我在寫一個後臺服務,需要處理一系列作業,作爲記錄存儲在sqlserver表中。該服務需要查找需要工作的最早的20個工作(where status = 'new'),將它們標記(set status = 'processing'),運行它們,然後更新作業。在數據庫中原子標記並返回一組行

這是我需要幫助的第一部分。可能有多個線程同時訪問數據庫,並且我想確保「mark & return」查詢以原子方式運行,或者幾乎如此運行。

該服務將花費相對較少的時間訪問數據庫,並且如果作業運行兩次,這並不是世界末日,所以我可能能夠接受小作業運行多次的概率,以提高簡單性在代碼中。

這樣做的最好方法是什麼?我爲我的數據層使用了linq-to-sql,但我認爲我必須將其下載到t-sql中。

回答

10

你的表的工作是一個隊列。編寫用戶表備份隊列是一個衆所周知的錯誤,因爲它會導致死鎖和協調問題。

最簡單的事情是放棄用戶表並使用真實的queue來代替。這將爲您在系統測試和驗證的代碼庫上提供無死鎖協議的空閒隊列。問題是隊列周圍的整個範例從INSERT和DELETE/UPDATE變爲SEND/RECEIVE。另一方面,通過內置隊列,您可以獲得一些非常強大的免費禮品,即Activationcorrelated items locking

如果你想繼續向下用戶表支持隊列的路徑,則第二最重要的技巧以書面用戶表隊列是使用UPDATE ... OUTPUT:

WITH cte AS (
    SELECT TOP(20) status, id, ... 
    FROM table WITH (ROWLOCK, READPAST, UPDLOCK) 
    WHERE status = 'new' 
    ORDER BY enqueue_time) 
UPDATE cte 
    SET status = 'processing' 
OUTPUT 
    INSERTED.id, ... 

的CTE語法只是爲了方便地放置TOP和ORDER BY,查詢可以使用派生表進行編寫,就像esily一樣。你不能使用直接更新...TOP,因爲UPDATE不支持ORDER BY,並且你需要這個來滿足你的需求中「最古老」的部分。需要鎖提示以促進並行處理線程之間的高度一致性。

我說這是第二重要的把戲。最重要的是你如何組織桌子。對於隊列,其必須(status, enqueue_time)聚類。如果你沒有正確地組織表格,最終會導致死鎖。先發制人的評論:在這種情況下,碎片是相當重要的。

+0

即使在使用3個提示後,您是否可以解釋爲什麼如果表未由(status,enqueue_time)聚簇,會出現死鎖? – 2012-06-22 05:14:28

+0

我不知道OUTPUT子句,它與提示一起提供了一個完整的解決方案。這在SO上回答了我自己的問題。 – 2012-11-09 14:23:07

8

請在這裏看到我的答案:SQL Server Process Queue Race Condition它也一次管理20行。

基本上,SQL Server中使用提示ROWLOCK,READPAST和UPDLOCK來管理併發性和輪詢非常簡單。

我不能對LINQ的評論,但交易仍有打開併發的問題:你需要使用我提到

+0

您的其他文章非常有幫助。我錯過了三個提示中的一個。 – 2012-11-09 14:23:56

1

我知道這是題外話了提示,但這個你可以使用MSMQ。消息隊列將按順序放置作業,並且它是線程安全的。您也可以爲MSMQ管理自己分配優先級。你可以使用read或peek從隊列中刪除一條消息,或者只是看看那裏有什麼。您可以使用命令設計模式來幫助您解決這個問題。

+0

排隊是答案,但爲什麼MSMQ在SQL Server自帶內置隊列時? – 2009-11-30 17:43:31

+0

我使用它們的方式是控制過程。當我排隊的東西我根本不使用數據庫。所以任何一個清單工作者都可以找到工作去做。我用5臺運行10個進程的計算機進行了測試,我從未遇到併發問題。我想這取決於你想排隊居住的地方。 – 2009-11-30 21:13:18

0

難道不僅僅是像在事務中運行T-SQL一樣簡單,還是我錯過了某些東西?

4

大廈gbn's answer ...

如果你使用SQL Server 2005或更高版本,您可以通過在UPDATE語句中使用的OUTPUT clause原子返回更新行:

UPDATE TOP (20) your_table 
SET status = 'processing' 
OUTPUT INSERTED.* 
FROM your_table WITH (ROWLOCK, READPAST, UPDLOCK) 
WHERE status = 'new' 
相關問題