本文使用乙個例項來說明如何使用樂觀鎖定和悲觀鎖定來解決多使用者併發的環境裡,其他使用者已經把你要修改的資料進行了修改而造成資料的不一致的問題。
在實際的多使用者併發訪問的生產環境裡邊,我們經常要盡可能的保持資料的一致性。而其中最典型的例子就是我們從表裡邊讀取資料,檢查驗證後對資料進行修改,然後寫回到資料庫中。在讀取和寫入的過程中,如果在多使用者併發的環境裡邊,其他使用者已經把你要修改的資料進行了修改是非常有可能發生的情況,這樣就造成了資料的不一致性。解決這樣的辦法,sql server提出了樂觀鎖定和悲觀鎖定的概念,下邊我以乙個例項來說明如何使用樂觀鎖定和悲觀鎖定來解決這樣的問題。
/*建立測試表:card,代表乙個真實的卡庫,供使用者註冊。使用者要從裡邊選出乙個未使用的卡,也就是f_flag=0的卡,給使用者註冊:更新f_name,f_time,f_flag欄位。如果出現兩個使用者同時更新一張卡的情況,是不能容忍的,也就是我們所說的資料不一致行。*/
create table card(f_cardno varchar(20),f_name varchar(20),f_flag bit,f_time datetime)
goinsert card(f_cardno,f_flag) select '1111-1111',0
insert card(f_cardno,f_flag) select '1111-1112',0
insert card(f_cardno,f_flag) select '1111-1113',0
insert card(f_cardno,f_flag) select '1111-1114',0
insert card(f_cardno,f_flag) select '1111-1115',0
insert card(f_cardno,f_flag) select '1111-1116',0
insert card(f_cardno,f_flag) select '1111-1117',0
insert card(f_cardno,f_flag) select '1111-1118',0
insert card(f_cardno,f_flag) select '1111-1119',0
insert card(f_cardno,f_flag) select '1111-1110',0
go
-- 下邊是我們經常使用的更新方案如下:
declare @cardno varchar(20)
begin tran
-- 選擇一張未使用的卡
select top 1 @cardno=f_cardno
from card where f_flag=0
-- 延遲50秒,模擬併發訪問.
waitfor delay '000:00:50'
-- 把剛才選擇出來的卡進行註冊.
update card
set f_name=user,
f_time=getdate(),
f_flag=1
where f_cardno=@cardno
commit
問題:如果我們在同一視窗執行同一段**,但是去掉了waitfor delay子句。兩邊執行完畢後,我們發現儘管執行了兩次註冊,但是只註冊了一張卡,也就是兩個人註冊了同一張卡。
悲觀鎖定解決方案
-- 我們只要對上邊的**做微小的改變就可以實現悲觀的鎖定。
declare @cardno varchar(20)
begin tran
-- 選擇一張未使用的卡
select top 1 @cardno=f_cardno
from card with (updlock) where f_flag=0
-- 延遲50秒,模擬併發訪問.
waitfor delay '000:00:50'
-- 把剛才選擇出來的卡進行註冊
.
update card
set f_name=user,
f_time=getdate(),
f_flag=1
where f_cardno=@cardno
commit注意其中的區別了嗎?with(updlock),是的,我們在查詢的時候使用了with(updlock)選項,在查詢記錄的時候我們就對記錄加上了更新鎖,表示我們即將對次記錄進行更新。注意更新鎖和共享鎖是不衝突的,也就是其他使用者還可以查詢此表的內容,但是和更新鎖和排它鎖是衝突的。所以其他的更新使用者就會阻塞。如果我們在另外乙個視窗執行此**,同樣不加waifor delay子句。兩邊執行完畢後,我們發現成功的註冊了兩張卡。可能我們已經發現了悲觀鎖定的缺點:當乙個使用者進行更新的事務的時候,其他更新使用者必須排隊等待,即使那個使用者更新的不是同一條記錄。
樂觀鎖定解決方案
-- 首先我們在card表裡邊加上一列f_timestamp 列,該列是varbinary(8)型別。但是在更新的時候這個值會自動增長。
alter table card add f_timestamp timestamp not null
-- 悲觀鎖定
declare @cardno varchar(20)
declare @timestamp varbinary(8)
declare @rowcount int
begin tran
-- 取得卡號和原始的時間戳值
select top 1 @cardno=f_cardno,
@timestamp=f_timestamp
from card
where f_flag=0
-- 延遲50秒,模擬併發訪問.
waitfor delay '000:00:50'
-- 註冊卡,但是要比較時間戳是否發生了變化.如果沒有發生變化.更新成功.如果發生變化,更新失敗.
update card
set f_name=user,
f_time=getdate(),
f_flag=1
where f_cardno=@cardno and f_timestamp=@timestamp
set @rowcount=@@rowcount
if @rowcount=1
begin
print '更新成功!'
commit
endelse if @rowcount=0
begin
if exists(select 1 from card where f_cardno=@cardno)
begin
print '此卡已經被另外乙個使用者註冊!'
rollback tran
endelse
begin
print '並不存在此卡!'
rollback tran
endend在另外乙個視窗裡邊執行沒有waitfor的**,註冊成功後,返回原來的視窗,我們就會發現到時間後它顯示的提示是此卡以被另外乙個使用者註冊的提示。很明顯,這樣我們也可以避免兩個使用者同時註冊一張卡的現象的出現。同時,使用這種方法的另外乙個好處是沒有使用更新鎖,這樣增加的系統的併發處理能力。
上邊我詳細介紹了樂觀鎖定和悲觀鎖定的使用方法,在實際生產環境裡邊,如果併發量不大,我們完全可以使用悲觀鎖定的方法,因為這種方法使用起來非常方便和簡單。但是如果系統的併發非常大的話,悲觀鎖定會帶來非常大的效能問題,所以我們就要選擇樂觀鎖定的方法。
SQL SERVER樂觀鎖定和悲觀鎖定
在實際的多使用者併發訪問的生產環境裡邊,我們經常要盡可能的保持資料的一致性。而其中 最典型的例子就是我們從表裡邊讀取資料,檢查驗證後對資料進行修改,然後寫回到資料庫 中。在讀取和寫入的過程中,如果在多使用者併發的環境裡邊,其他使用者已經把你要修改的資料 進行了修改是非常有可能發生的情況,這樣就造成了...
SQL Server樂觀鎖定和悲觀鎖定例項
在sql server中,提供了幾種表級鎖定提示 locking hints 通過使用這些悲觀鎖,可以在多個同時修改資料庫的使用者間實現悲觀併發控制。對資料庫加鎖後,其他人不可操作,直到加鎖使用者用commit命令或rollback命令解鎖。begin tran 開始事務 應用select語句實現對...
SQL Server樂觀鎖定和悲觀鎖定例項
在實際的多使用者併發訪問的生產環境裡邊,我們經常要盡可能的保持資料的一致性。而其中最典型的例子就是我們從表裡邊讀取資料,檢查驗證後對資料進行修改,然後寫回到資料庫中。在讀取和寫入的過程中,如果在多使用者併發的環境裡邊,其他使用者已經把你要修改的資料進行了修改是非常有可能發生的情況,這樣就造成了資料的...