關(guān)鍵字: oracle的悲觀鎖和樂觀鎖
為了得到最大的性能,一般數(shù)據(jù)庫(kù)都有并發(fā)機(jī)制,不過(guò)帶來(lái)的問(wèn)題就是數(shù)據(jù)訪問(wèn)的沖突。為了解決這個(gè)問(wèn)題,大多數(shù)數(shù)據(jù)庫(kù)用的方法就是數(shù)據(jù)的鎖定。 

數(shù)據(jù)的鎖定分為兩種方法,第一種叫做悲觀鎖,第二種叫做樂觀鎖。什么叫悲觀鎖呢,悲觀鎖顧名思義,就是對(duì)數(shù)據(jù)的沖突采取一種悲觀的態(tài)度,也就是說(shuō)假設(shè)數(shù)據(jù)肯定會(huì)沖突,所以在數(shù)據(jù)開始讀取的時(shí)候就把數(shù)據(jù)鎖定住。而樂觀鎖就是認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè),如果發(fā)現(xiàn)沖突了,則讓用戶返回錯(cuò)誤的信息,讓用戶決定如何去做。 

先從悲觀鎖開始說(shuō)。在SqlServer等其余很多數(shù)據(jù)庫(kù)中,數(shù)據(jù)的鎖定通常采用頁(yè)級(jí)鎖的方式,也就是說(shuō)對(duì)一張表內(nèi)的數(shù)據(jù)是一種串行化的更新插入機(jī)制,在任何時(shí)間同一張表只會(huì)插1條數(shù)據(jù),別的想插入的數(shù)據(jù)要等到這一條數(shù)據(jù)插完以后才能依次插入。帶來(lái)的后果就是性能的降低,在多用戶并發(fā)訪問(wèn)的時(shí)候,當(dāng)對(duì)一張表進(jìn)行頻繁操作時(shí),會(huì)發(fā)現(xiàn)響應(yīng)效率很低,數(shù)據(jù)庫(kù)經(jīng)常處于一種假死狀態(tài)。而Oracle用的是行級(jí)鎖,只是對(duì)想鎖定的數(shù)據(jù)才進(jìn)行鎖定,其余的數(shù)據(jù)不相干,所以在對(duì)Oracle表中并發(fā)插數(shù)據(jù)的時(shí)候,基本上不會(huì)有任何影響。 

注:對(duì)于悲觀鎖是針對(duì)并發(fā)的可能性比較大,而一般在我們的應(yīng)用中用樂觀鎖足以。 


Oracle的悲觀鎖需要利用一條現(xiàn)有的連接,分成兩種方式,從SQL語(yǔ)句的區(qū)別來(lái)看,就是一種是for update,一種是for update nowait的形式。比如我們看一個(gè)例子。首先建立測(cè)試用的數(shù)據(jù)庫(kù)表。 

Sql代碼 
  1. 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用戶的表,把數(shù)據(jù)copy到我們的test表中。首先我們看一下for update鎖定方式。首先我們執(zhí)行如下的select for update語(yǔ)句。 

Sql代碼 
  1. select * from test where id = 10 for update  


通過(guò)這條檢索語(yǔ)句鎖定以后,再開另外一個(gè)sql*plus窗口進(jìn)行操作,再把上面這條sql語(yǔ)句執(zhí)行一便,你會(huì)發(fā)現(xiàn)sqlplus好像死在那里了,好像檢索不到數(shù)據(jù)的樣子,但是也不返回任何結(jié)果,就屬于卡在那里的感覺。這個(gè)時(shí)候是什么原因呢,就是一開始的第一個(gè)Session中的select for update語(yǔ)句把數(shù)據(jù)鎖定住了。由于這里鎖定的機(jī)制是wait的狀態(tài)(只要不表示nowait那就是wait),所以第二個(gè)Session(也就是卡住的那個(gè)sql*plus)中當(dāng)前這個(gè)檢索就處于等待狀態(tài)。當(dāng)?shù)谝粋€(gè)session最后commit或者rollback之后,第二個(gè)session中的檢索結(jié)果就是自動(dòng)跳出來(lái),并且也把數(shù)據(jù)鎖定住。不過(guò)如果你第二個(gè)session中你的檢索語(yǔ)句如下所示。 

Sql代碼 
  1. select * from test where id = 10  



也就是沒有for update這種鎖定數(shù)據(jù)的語(yǔ)句的話,就不會(huì)造成阻塞了。另外一種情況,就是當(dāng)數(shù)據(jù)庫(kù)數(shù)據(jù)被鎖定的時(shí)候,也就是執(zhí)行剛才for update那條sql以后,我們?cè)诹硗庖粋€(gè)session中執(zhí)行for update nowait后又是什么樣呢。比如如下的sql語(yǔ)句。由于這條語(yǔ)句中是制定采用nowait方式來(lái)進(jìn)行檢索,所以當(dāng)發(fā)現(xiàn)數(shù)據(jù)被別的session鎖定中的時(shí)候,就會(huì)迅速返回ORA-00054錯(cuò)誤,內(nèi)容是資源正忙, 但指定以 NOWAIT 方式獲取資源。所以在程序中我們可以采用nowait方式迅速判斷當(dāng)前數(shù)據(jù)是否被鎖定中,如果鎖定中的話,就要采取相應(yīng)的業(yè)務(wù)措施進(jìn)行處理。 

Sql代碼 
  1. select * from test where id = 10 for update nowait  



那這里另外一個(gè)問(wèn)題,就是當(dāng)我們鎖定住數(shù)據(jù)的時(shí)候,我們對(duì)數(shù)據(jù)進(jìn)行更新和刪除的話會(huì)是什么樣呢。比如同樣,我們讓第一個(gè)Session鎖定住id=10的那條數(shù)據(jù),我們?cè)诘诙€(gè)session中執(zhí)行如下語(yǔ)句。 

Sql代碼 
  1. update test set value=2 where id = 10  



這個(gè)時(shí)候我們發(fā)現(xiàn)update語(yǔ)句就好像select for update語(yǔ)句一樣也停住卡在這里,當(dāng)你第一個(gè)session放開鎖定以后update才能正常運(yùn)行。當(dāng)你update運(yùn)行后,數(shù)據(jù)又被你update 語(yǔ)句鎖定住了,這個(gè)時(shí)候只要你update后還沒有commit,別的session照樣不能對(duì)數(shù)據(jù)進(jìn)行鎖定更新等等。 

總之,Oracle中的悲觀鎖就是利用Oracle的Connection對(duì)數(shù)據(jù)進(jìn)行鎖定。在Oracle中,用這種行級(jí)鎖帶來(lái)的性能損失是很小的,只是要注意程序邏輯,不要給你一不小心搞成死鎖了就好。而且由于數(shù)據(jù)的及時(shí)鎖定,在數(shù)據(jù)提交時(shí)候就不呼出現(xiàn)沖突,可以省去很多惱人的數(shù)據(jù)沖突處理。缺點(diǎn)就是你必須要始終有一條數(shù)據(jù)庫(kù)連接,就是說(shuō)在整個(gè)鎖定到最后放開鎖的過(guò)程中,你的數(shù)據(jù)庫(kù)聯(lián)接要始終保持住。與悲觀鎖相對(duì)的,我們有了樂觀鎖。樂觀鎖一開始也說(shuō)了,就是一開始假設(shè)不會(huì)造成數(shù)據(jù)沖突,在最后提交的時(shí)候再進(jìn)行數(shù)據(jù)沖突檢測(cè)。在樂觀鎖中,我們有3種 

常用的做法來(lái)實(shí)現(xiàn)。 

[1]第一種就是在數(shù)據(jù)取得的時(shí)候把整個(gè)數(shù)據(jù)都copy到應(yīng)用中,在進(jìn)行提交的時(shí)候比對(duì)當(dāng)前數(shù)據(jù)庫(kù)中的數(shù)據(jù)和開始的時(shí)候更新前取得的數(shù)據(jù)。當(dāng)發(fā)現(xiàn)兩個(gè)數(shù)據(jù)一模一樣以后,就表示沒有沖突可以提交,否則則是并發(fā)沖突,需要去用業(yè)務(wù)邏輯進(jìn)行解決。 

[2]第二種樂觀鎖的做法就是采用版本戳,這個(gè)在Hibernate中得到了使用。采用版本戳的話,首先需要在你有樂觀鎖的數(shù)據(jù)庫(kù)table上建立一個(gè)新的column,比如為number型,當(dāng)你數(shù)據(jù)每更新一次的時(shí)候,版本數(shù)就會(huì)往上增加1。比如同樣有2個(gè)session同樣對(duì)某條數(shù)據(jù)進(jìn)行操作。兩者都取到當(dāng)前的數(shù)據(jù)的版本號(hào)為1,當(dāng)?shù)谝粋€(gè)session進(jìn)行數(shù)據(jù)更新后,在提交的時(shí)候查看到當(dāng)前數(shù)據(jù)的版本還為1,和自己一開始取到的版本相同。就正式提交,然后把版本號(hào)增加1,這個(gè)時(shí)候當(dāng)前數(shù)據(jù)的版本為2。當(dāng)?shù)诙€(gè)session也更新了數(shù)據(jù)提交的時(shí)候,發(fā)現(xiàn)數(shù)據(jù)庫(kù)中版本為2,和一開始這個(gè) session取到的版本號(hào)不一致,就知道別人更新過(guò)此條數(shù)據(jù),這個(gè) 

時(shí)候再進(jìn)行業(yè)務(wù)處理,比如整個(gè)Transaction都Rollback等等操作。在用版本戳的時(shí)候,可以在應(yīng)用程序側(cè)使用版本戳的驗(yàn)證,也可以在數(shù)據(jù)庫(kù)側(cè)采用Trigger(觸發(fā)器)來(lái)進(jìn)行驗(yàn)證。不過(guò)數(shù)據(jù)庫(kù)的Trigger的性能開銷還是比較的大,所以能在應(yīng)用側(cè)進(jìn)行驗(yàn)證的話還是推薦不用 Trigger。 

[3]第三種做法和第二種做法有點(diǎn)類似,就是也新增一個(gè)Table的Column,不過(guò)這次這個(gè)column是采用timestamp型,存儲(chǔ)數(shù)據(jù)最后更新的時(shí)間。在Oracle9i以后可以采用新的數(shù)據(jù)類型,也就是timestamp with time zone類型來(lái)做時(shí)間戳。這種Timestamp的數(shù)據(jù)精度在Oracle的時(shí)間類型中是最高的,精確到微秒(還沒與到納秒的級(jí)別),一般來(lái)說(shuō),加上數(shù)據(jù)庫(kù)處理時(shí)間和人的思考動(dòng)作時(shí)間,微秒級(jí)別是非常非常夠了,其實(shí)只要精確到毫秒甚至秒都應(yīng)該沒有什么問(wèn)題。和剛才的版本戳類似,也是在更新提交的時(shí)候檢查當(dāng)前數(shù)據(jù)庫(kù)中數(shù)據(jù)的時(shí)間戳和自己更新前取到的時(shí)間戳進(jìn)行對(duì)比,如果一致則OK,否則就是版本沖突。如果不想把代碼寫在程序中或者由于別的原因無(wú)法把代碼寫在現(xiàn)有的程序中,也可以把這個(gè)時(shí)間戳樂觀鎖邏輯寫在Trigger或者存儲(chǔ)過(guò)程中。

Oracle鎖表(網(wǎng)上抄來(lái)的)

鎖定類型

               行級(jí)鎖

               表級(jí)鎖

行級(jí)鎖

         ---- 行被排他鎖定

         ----在某行的鎖被釋放之前,其他用戶不能修改此行

         ----使用 commit 或 rollback 命令釋放鎖

         ----Oracle 通過(guò)使用 INSERT、UPDATE 和 SELECT…FOR UPDATE 語(yǔ)句自動(dòng)獲取行級(jí)鎖

SELECT…FOR UPDATE 子句

―在表的一行或多行上放置排他鎖

―用于防止其他用戶更新該行

―可以執(zhí)行除更新之外的其他操作

―select * from goods where gid=1001

―for update of gname;

―只有該用戶提交事務(wù),其他用戶才能夠更新gname

FOR UPDATE WAIT 子句

―Oracle9i 中的新增功能

―防止無(wú)限期地等待鎖定的行

―等待間隔必須指定為數(shù)值文字

―等待間隔不能是表達(dá)式、賦值變量或 PL/SQL

變量

―select * from goods where gid=1001 for update of gname wait 3

―等待用戶釋放更新鎖的時(shí)間為3秒,否則超時(shí)。

表級(jí)鎖

―保護(hù)表的數(shù)據(jù)

―在多個(gè)用戶同時(shí)訪問(wèn)數(shù)據(jù)時(shí)確保數(shù)據(jù)的完整性

―可以設(shè)置為三種模式:共享、共享更新和 排他

          語(yǔ)法:lock table<table_name>in<mode>;

共享鎖

―鎖定表

―僅允許其他用戶執(zhí)行查詢操作

―不能插入、更新和刪除

―多個(gè)用戶可以同時(shí)在同一表中放置此鎖

―lock table table_name

―in share mode [nowait];

― rollback 和commit 命令釋放鎖

― nowait 關(guān)鍵字告訴其他用戶不用等待

共享更新鎖

―鎖定要被更新的行

―允許其他用戶同時(shí)查詢、插入、更新未被鎖定的行

―在 SELECT 語(yǔ)句中使用“FOR UPDATE”子句,可以強(qiáng)制使用共享更新鎖

―允許多個(gè)用戶同時(shí)鎖定表的不同行

加鎖的兩種方法

        1        lock table tab_name in share update mode;

        2         select column1,column2

            from goods

            where goods

            where gid=1001

            for update of column1,column2

排他鎖

―與其他兩種鎖相比,排他鎖是限制性最強(qiáng)的表鎖

―僅允許其他用戶查詢數(shù)據(jù)

―不允許執(zhí)行插入、刪除和更新操作

―在同一時(shí)間僅允許一位用戶在表上放置排他鎖

―共享鎖與此相反

         lock table tab_name in exclusive mode;

         lock table<表名>[<表名>]...

                   in share mode [nowait]

  

         lock table< 表名>[<表名>]...

         in exclusive mode [nowait]

         lock table<表名>[<表名>]...

         in share update mode[nowait]



久久不醉