前言
之前對於並發這一塊確實接觸的比較少,自從遇到現在的老大,每寫完一塊老大都會過目一下然后給出意見,期間確實收獲不少,接下來有幾篇會來講解SQL Server中關於並發這一塊的內容,有的是總結,有的是學習,若有錯誤見解請批評性指出。
SQL Server並發處理隊列數據問題
在我們的項目中對於購買產品的用戶會對應分配卡密,同時會更新其卡密的狀態為已使用,所以當出現並發時此時我們不加以控制會導致同一個卡號和密碼被不同的用戶所使用,這樣的情況是不能允許的,此時我們迫切需要解決對卡密使用后的更新和產生的並發。所以有了此文的產生。我們接下來來創建測試表。
CREATE TABLE Test ( Id INT IDENTITY(1, 1) NOT NULL PRIMARY KEY, Other VARCHAR(100)) GO
接下來我們插入十條測試數據
DECLARE @counter INT SELECT @counter = 1 WHILE (@counter <= 10) BEGIN INSERT INTO Test (Other) SELECT 'other action' + CAST(@counter AS VARCHAR) SELECT @counter = @counter + 1 END
接下來我們打開兩個會話運行如下SQL語句:
DECLARE @queueid INT BEGIN TRAN TRAN1 SELECT TOP 1 @queueid = Id FROM Test PRINT 'processing queueid # ' + CAST(@queueid AS VARCHAR) WAITFOR DELAY '00:00:10' DELETE FROM Test WHERE Id = @queueid COMMIT
此時我們看到打開的兩個會話會同時處理相同的行。
如上則不是我們想要的結果,此時我們再來在如上基礎上加一個更新鎖,然后SQL Server查詢引擎會不允許其他讀取者來獲取更新鎖,此時將能夠有效的處理對應對應的行記錄,但是會造成阻塞,如下:
DECLARE @queueid INT BEGIN TRAN TRAN1 SELECT TOP 1 @queueid = Id FROM Test WITH (updlock) PRINT 'processing queueid # ' + CAST(@queueid AS VARCHAR) WAITFOR DELAY '00:00:10' DELETE FROM Test WHERE Id = @queueid COMMIT
上述雖然能解決更新問題,但是此時會造成阻塞,一旦並發量比較大此時將造成長時間阻塞,當前正在執行的更新會話必須等待另外一個更新會話執行完畢同時釋放更新鎖。此時為了解決阻塞問題,在SQL Server中通過添加READPAST關鍵字來告訴SQL Server引擎一旦遇到被鎖住的行,你就跳過吧不用理會,所以不會再造成阻塞問題。此時最終的代碼將變成如下:
DECLARE @queueid INT BEGIN TRAN TRAN1 SELECT TOP 1 @queueid = Id FROM Test WITH (updlock) BEGIN TRAN TRAN1 SELECT TOP 1 @queueid = Id FROM Test WITH (UPDLOCK, READPAST) PRINT 'processing queueid # ' + CAST(@queueid AS VARCHAR) WAITFOR DELAY '00:00:10' DELETE FROM Test WHERE Id = @queueid COMMIT PRINT 'processing queueid # ' + CAST(@queueid AS VARCHAR) WAITFOR DELAY '00:00:10' DELETE FROM Test WHERE Id = @queueid COMMIT
通過UPDLOCK+READPAST結合使用將對於處理並發更新時,就像處理隊列數據一樣,但是不會造成阻塞,此時將給予我們最好的性能。我們結合上述所講,來查詢出數據並刪除對應數據且,不會出現重復刪除情況且不會導致阻塞,此時代碼將變成如下:
SET NOCOUNT ON DECLARE @queueid INT WHILE (SELECT COUNT(*) FROM Test WITH (updlock, readpast)) >= 1 BEGIN BEGIN TRAN TRAN1 SELECT TOP 1 @queueid = Id FROM Test WITH (updlock, readpast) PRINT 'processing queueid # ' + CAST(@queueid AS VARCHAR) WAITFOR DELAY '00:00:10' DELETE FROM Test WHERE Id = @queueid COMMIT END
總結
本文我們探討產生並發在SQL Server中如何不處於阻塞並且得到較好的性能,對於那種秒殺情況,這種方案不失為一種解決方案,請問你有何高見?