2010-02-25 19 views
4

這是從When I update/insert a single row should it lock the entire table?爲什麼行級別鎖定不能在SQL Server中正常工作?

的延續這是我的問題。

我有一張容納鎖的表,以便系統中的其他記錄不必鎖定公共資源,但仍可以排列任務,以便一次執行一個任務。

當我訪問此鎖表中的一條記錄時,我希望能夠鎖定它並更新它(只是一條記錄),而不需要其他任何進程都可以做同樣的事情。我可以通過鎖定提示來執行此操作,例如Uplock

雖然會發生什麼情況是,即使我使用ROWLOCK鎖定記錄,它阻止另一個進程的請求,以改變在同一個表中的完全不相關的行會也沿着指定的UPDLOCK提示與rowlock

您可以重新創建這個被製作表格

SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
SET ANSI_PADDING ON 
GO 
CREATE TABLE [dbo].[Locks](
    [ID] [int] IDENTITY(1,1) NOT NULL, 
    [LockName] [varchar](50) NOT NULL, 
    [Locked] [bit] NOT NULL, 
CONSTRAINT [PK_Locks] PRIMARY KEY CLUSTERED 
(
    [ID] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY] 
) ON [PRIMARY] 
GO 
SET ANSI_PADDING OFF 
GO 
ALTER TABLE [dbo].[Locks] ADD CONSTRAINT [DF_Locks_LockName] DEFAULT ('') FOR [LockName] 
GO 
ALTER TABLE [dbo].[Locks] ADD CONSTRAINT [DF_Locks_Locked] DEFAULT ((0)) FOR [Locked] 
GO 

一個鎖添加兩行鎖名稱= 'A',一個用於鎖名稱= 'B'

然後創建兩個查詢在同一時間在事務中運行:

查詢1:

Commit 
Begin transaction 
select * From Locks with (updlock rowlock) where LockName='A' 

查詢2:

select * From Locks with (updlock rowlock) where LockName='B' 

請注意,我離開事務打開,這樣就可以看到這個問題,因爲它不會沒有這個打開的事務是可見的。

當您運行查詢1把鎖是該行問題和鎖名稱=任何後續查詢」 A」將不得不等待。這種行爲是正確的。

如果這變得有點令人沮喪的是,當你運行你被阻塞,直到查詢1個完成甚至認爲這是不相關的記錄查詢2。如果您再運行查詢1就像我上面那樣,它會提交上一個事務,查詢2將運行,然後查詢1將再次鎖定記錄。

請提供一些建議,告訴我如何才能正確鎖定只有一行,並且不會阻止更新其他項目。

PS。更新其中一行後,Holdlock也無法產生正確的行爲。

回答

10

SQL Server中,鎖提示應用於掃描的對象,不匹配。

正常情況下,引擎在讀取對象(頁面等)時放置一個共享鎖,並在完成掃描後提起它們(或在事務中不擡起)。

但是,您指示引擎放置(並提起)彼此不兼容的更新鎖。

交易B鎖定,同時試圖將UPDLOCK放到已鎖定爲UPDLOCK的行的交易A上。

如果創建一個索引,並迫使其使用(所以沒有衝突的讀取不斷髮生),你的表不會鎖定:

CREATE INDEX ix_locks_lockname ON locks (lockname) 

Begin transaction 
select * From Locks with (updlock rowlock INDEX (ix_locks_lockname)) where LockName='A' 

Begin transaction 
select * From Locks with (updlock rowlock INDEX (ix_locks_lockname)) where LockName='B' 
+0

這是一個很好的答案,並感謝您發佈有用的代碼。有沒有辦法讓SQL默認這種模式? – Middletone 2010-02-25 16:27:32

+0

@Middletone:什麼是「默認」? – Quassnoi 2010-02-25 16:28:22

+0

對不起,我真的應該問如何讓SQL使用索引而不是表掃描,因爲這聽起來有點像是什麼導致它不使用表上的索引。有沒有辦法讓它始終鎖定主鍵,而不管它如何在表中找到記錄?這是我對違約的看法。 – Middletone 2010-02-25 16:36:52

1

對於查詢2,儘量使用READPAST提示 - 這(報價):

指定數據庫引擎不 讀取由其他 交易鎖定行。在大多數 的情況下, 頁面也是如此。當指定READPAST時,跳過 行級和頁級鎖 。也就是說,數據庫 引擎跳過過去的行或 代替阻塞當前 交易頁,直到鎖被釋放

這通常是在隊列處理類型的環境中使用 - 因此多個進程可拉斷隊列表中的下一個項目不會被其他進程阻塞(當然,使用UPDLOCK可以防止多個進程拾取同一行)。

編輯1:
如果您在LockName字段中沒有索引,可能會導致此問題。通過索引,查詢2可以對確切的行進行索引查找。但是如果沒有它,它將進行掃描(檢查每一行),這意味着第一個事務會阻止它。所以,如果它沒有索引,嘗試索引它。

+0

如果我使用READPAST和另一個進程已鎖定** ** b然後什麼都不會退還,這將是一樣很多問題。這也不能解決行級別鎖定無法正常工作的原始問題。 – Middletone 2010-02-25 16:07:25

+0

再次改變索引什麼都不做。我發佈了所有代碼的麻煩,所以請在發佈答案之前測試您的解決方案。 – Middletone 2010-02-25 16:20:28

+1

@Middletone - 夠公平的。很多時候,直接複製某人遇到的具體問題實際上是很困難的 - 除非這樣做,否則不可能總是說「這是解決您的問題的辦法」。我有工作解決方案,這正是我爲幫助你而設計的答案 - 加上我的時間和精力。 – AdaTheDev 2010-02-25 16:28:39

1

如果您想要在SQL Server中排隊,請使用UPDLOCK, ROWLOCK, READPAST提示。有用。

我會考慮改變你的方法,而不是試圖改變SQL Server行爲......