為了得到最大的效能,一般資料庫都有併發機制,不過帶來的問題就是資料訪問的衝突。為了解決這個問題,大多數資料庫用的方法就是資料的鎖定。
資料的鎖定分為兩種方法,第一種叫做悲觀鎖,第二種叫做樂觀鎖。什麼叫悲觀鎖呢,悲觀鎖顧名思義,就是對資料的衝突採取一種悲觀的態度,也就是說假設資料肯定會衝突,所以在資料開始讀取的時候就把資料鎖定住。而樂觀鎖就是認為資料一般情況下不會造成衝突,所以在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果發現衝突了,則讓使用者返回錯誤的資訊,讓使用者決定如何去做。
先從悲觀鎖開始說。在sqlserver等其餘很多資料庫中,資料的鎖定通常採用頁級鎖的方式,也就是說對一張表內的資料是一種序列化的更新插入機制,在任何時間同一張表只會插1條資料,別的想插入的資料要等到這一條資料插完以後才能依次插入。帶來的後果就是效能的降低,在多使用者併發訪問的時候,當對一張表進行頻繁操作時,會發現響應效率很低,資料庫經常處於一種假死狀態。而oracle用的是行級鎖,只是對想鎖定的資料才進行鎖定,其餘的資料不相干,所以在對oracle表中併發插資料的時候,基本上不會有任何影響。
注:對於悲觀鎖是針對併發的可能性比較大,而一般在我們的應用中用樂觀鎖足以。
oracle的悲觀鎖需要利用一條現有的連線,分成兩種方式,從sql語句的區別來看,就是一種是for update,一種是for update nowait的形式。比如我們看乙個例子。首先建立測試用的資料庫表。
create table test(id,name,location,value,constraint test_pk primary key(id))as select deptno, dname, loc, 1 from scott.dept
這裡我們利用了oracle的sample的scott使用者的表,把資料copy到我們的test表中。首先我們看一下for update鎖定方式。首先我們執行如下的select for update語句。
select * from test where id = 10 for update
通過這條檢索語句鎖定以後,再開另外乙個sql*plus視窗進行操作,再把上面這條sql語句執行一便,你會發現sqlplus好像死在那裡了,好像檢索不到資料的樣子,但是也不返回任何結果,就屬於卡在那裡的感覺。這個時候是什麼原因呢,就是一開始的第乙個session中的select for update語句把資料鎖定住了。由於這裡鎖定的機制是wait的狀態(只要不表示nowait那就是wait),所以第二個session(也就是卡住的那個sql*plus)中當前這個檢索就處於等待狀態。當第乙個session最後commit或者rollback之後,第二個session中的檢索結果就是自動跳出來,並且也把資料鎖定住。不過如果你第二個session中你的檢索語句如下所示。
select * from test where id = 10
也就是沒有for update這種鎖定資料的語句的話,就不會造成阻塞了。另外一種情況,就是當資料庫資料被鎖定的時候,也就是執行剛才for update那條sql以後,我們在另外乙個session中執行for update nowait後又是什麼樣呢。比如如下的sql語句。 由於這條語句中是制定採用nowait方式來進行檢索,所以當發現資料被別的session鎖定中的時候,就會迅速返回ora-00054錯誤,內容是資源正忙, 但指定以 nowait 方式獲取資源。所以在程式中我們可以採用nowait方式迅速判斷當前資料是否被鎖定中,如果鎖定中的話,就要採取相應的業務措施進行處理。
select * from test where id = 10 for update nowait
那這裡另外乙個問題,就是當我們鎖定住資料的時候,我們對資料進行更新和刪除的話會是什麼樣呢。比如同樣,我們讓第乙個session鎖定住id=10的那條資料,我們在第二個session中執行如下語句。
update test set value=2 where id = 10
這個時候我們發現update語句就好像select for update語句一樣也停住卡在這裡,當你第乙個session放開鎖定以後update才能正常執行。當你update執行後,資料又被你update語句鎖定住了,這個時候只要你update後還沒有commit,別的session照樣不能對資料進行鎖定更新等等。
總之,oracle中的悲觀鎖就是利用oracle的connection對資料進行鎖定。在oracle中,用這種行級鎖帶來的效能損失是很小的,只是要注意程式邏輯,不要給你一不小心搞成死鎖了就好。而且由於資料的及時鎖定,在資料提交時候就不呼出現衝突,可以省去很多惱人的資料衝突處理。缺點就是你必須要始終有一條資料庫連線,就是說在整個鎖定到最後放開鎖的過程中,你的資料庫聯接要始終保持住。與悲觀鎖相對的,我們有了樂觀鎖。樂觀鎖一開始也說了,就是一開始假設不會造成資料衝突,在最後提交的時候再進行資料衝突檢測。在樂觀鎖中,我們有3種
常用的做法來實現。
[1]第一種就是在資料取得的時候把整個資料都copy到應用中,在進行提交的時候比對當前資料庫中的資料和開始的時候更新前取得的資料。當發現兩個資料一模一樣以後,就表示沒有衝突可以提交,否則則是併發衝突,需要去用業務邏輯進行解決。
[2]第二種樂觀鎖的做法就是採用版本戳,這個在hibernate中得到了使用。採用版本戳的話,首先需要在你有樂觀鎖的資料庫table上建立乙個新的column,比如為number型,當你資料每更新一次的時候,版本數就會往上增加1。比如同樣有2個session同樣對某條資料進行操作。兩者都取到當前的資料的版本號為1,當第乙個session進行資料更新後,在提交的時候檢視到當前資料的版本還為1,和自己一開始取到的版本相同。就正式提交,然後把版本號增加1,這個時候當前資料的版本為2。當第二個session也更新了資料提交的時候,發現資料庫中版本為2,和一開始這個session取到的版本號不一致,就知道別人更新過此條資料,這個
時候再進行業務處理,比如整個transaction都rollback等等操作。在用版本戳的時候,可以在應用程式側使用版本戳的驗證,也可以在資料庫側採用trigger(觸發器)來進行驗證。不過資料庫的trigger的效能開銷還是比較的大,所以能在應用側進行驗證的話還是推薦不用trigger。
[3]第三種做法和第二種做法有點類似,就是也新增乙個table的column,不過這次這個column是採用timestamp型,儲存資料最後更新的時間。在oracle9i以後可以採用新的資料型別,也就是timestamp with time zone型別來做時間戳。這種timestamp的資料精度在oracle的時間型別中是最高的,精確到微秒(還沒與到納秒的級別),一般來說,加上資料庫處理時間和人的思考動作時間,微秒級別是非常非常夠了,其實只要精確到毫秒甚至秒都應該沒有什麼問題。和剛才的版本戳類似,也是在更新提交的時候檢查當前資料庫中資料的時間戳和自己更新前取到的時間戳進行對比,如果一致則ok,否則就是版本衝突。如果不想把**寫在程式中或者由於別的原因無法把**寫在現有的程式中,也可以把這個時間戳樂觀鎖邏輯寫在trigger或者儲存過程中。
悲觀鎖和樂觀鎖
1.悲觀鎖,正如其名,它指的是對資料被外界 包括本系統當前的其他事務,以及來自外部系統的事務處理 修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。悲觀鎖的實現,往往依靠資料庫提供的鎖機制 也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無...
悲觀鎖和樂觀鎖
前幾天有人問了我乙個問題,說如果資料庫某些操作不用事務,那麼又需要保持資料的一致性,那麼該用什麼方法替代事務。我就想到了悲觀鎖和樂觀鎖的思想,下面我解釋一下在資料庫中的悲觀鎖和樂觀鎖 1.悲觀鎖就是把資料庫的一些操作,放在事務當中,依賴資料庫的隔離級別,實現對資料修改的封鎖,這樣做資料一致性可以保持...
悲觀鎖和樂觀鎖
悲觀鎖 pessimistic lock 顧名思義,就是很悲觀,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會block直到它拿到鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。樂觀鎖 optim...