Oracle資料庫資料鎖機制解析(zt)

lwitpub發表於2009-10-12

為了得到最大的效能,一般資料庫都有併發機制,不過帶來的問題就是資料訪問的衝突。為了解決這個問題,大多數資料庫用的方法就是資料的鎖定。

資料的鎖定分為兩種方法,第一種叫做悲觀鎖,第二種叫做樂觀鎖。什麼叫悲觀鎖呢,悲觀鎖顧名思義,就是對資料的衝突採取一種悲觀的態度,也就是說假設資料肯定會衝突,所以在資料開始讀取的時候就把資料鎖定住。而樂觀鎖就是認為資料一般情況下不會造成衝突,所以在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果發現衝突了,則讓使用者返回錯誤的資訊,讓使用者決定如何去做。

先從悲觀鎖開始說。Oracle用的是行級鎖,只是對想鎖定的資料才進行鎖定,其餘的資料不相干,所以在對Oracle表中併發插資料的時候,基本上不會有任何影響。Oracle的悲觀鎖需要利用一條現有的連線,分成兩種方式,從SQL語句的區別來看,就是一種是for update,一種是for update nowait的形式。比如我們看一個例子。首先建立測試用的資料庫表。

SQL> CREATE TABLE TEST(ID,NAME,LOCATION,VALUE,CONSTRAINT test_pk PRIMARY KEY(ID))AS SELECT deptno, dname, loc, 1 FROM scott.dept;

表已建立。


首先我們執行如下的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中,用這種行級鎖帶來的效能損失是很小的,只是要注意程式邏輯,不要給你一不小心搞成死鎖了就好。而且由於資料的及時鎖定,在資料提交時候就不撥出現衝突,可以省去很多惱人的資料衝突處理。缺點就是你必須要始終有一條資料庫連線,就是說在整個鎖定到最後放開鎖的過程中,你的資料庫聯接要始終保持住。

與悲觀鎖相對的,我們有了樂觀鎖。樂觀鎖一開始也說了,就是一開始假設不會造成資料衝突,在最後提交的時候再進行資料衝突檢測。在樂觀鎖中,最好的方法就是採用Oracle 10g所帶有的ORA_ROWSCN函式來進行樂觀鎖。ORA_ROWSCN是根據系統最後更新時間來進行計算。這個ORA_ROWSCN在預設情況下是採用資料塊為單位的,也就是一個資料庫塊(block)上共享一個ORA_ROWSCN,當資料更新的時候,這個block快的ORA_ROWSCN就會自動更新。所以在預設情況下的話,有可能出現假衝突的情況。比如A,B,C,D四條資料都在一個block上,這個時候A資料更新了, ORA_ROWSCN也會更新,這個時候因為ABCD四條資料儲存在一個block上,所以BCD的ORA_ROWSCN也更新過了,其實BCD三條資料並沒有更新過,這個就造成了假更新的情況出現。如下SQL語句:SQL> select id,name, location,value,ora_rowscn from test;

        ID NAME           LOCATION           VALUE ORA_ROWSCN
---------- -------------- ------------- ---------- ----------
        10 ACCOUNTING     NEW YORK               1    4926165
        20 RESEARCH       DALLAS                 1    4926165
        30 SALES          CHICAGO                1    4926165
        40 OPERATIONS     BOSTON                 1    4926165

然後執行如下的更新語句:update test set value=2 where id = 20

在資料commit過後,會發現ORA_ROWSCN都發生了變化,如下所示:

SQL> select id,name, location,value,ora_rowscn from test;

 ID    NAME           LOCATION       VALUE ORA_ROWSCN
---------- -------------- ------------- ---------- ----------
        10 ACCOUNTING     NEW YORK               1    4926900
        20 RESEARCH       DALLAS                 2    4926900
        30 SALES          CHICAGO                1    4926900
        40 OPERATIONS     BOSTON                 1    4926900
對於這種情況還是可以有辦法解決的,就是利用Oracle 9i提供的ROWDEPENDENCIES建表關鍵字,這個關鍵字在Oracle9i中是為了增加行依賴性跟蹤特性的,支援推進複製。在 Oracle10g中有可以用來做行級別的ORA_ROWSCN用了。用這個關鍵字建表以後,在每行會增加一個隱藏的COLUMN,所以每行會增加6個byte的開銷。建表語句如下所示:

SQL> CREATE TABLE TEST2(ID,NAME,LOCATION,VALUE,CONSTRAINT test2_pk PRIMARY KEY(ID))ROWDEPENDENCIES AS SELECT deptno, dname, loc, 1 FROM scott.dept;

表已建立。

SQL> select id,name, location,value,ora_rowscn from test2;

        ID NAME           LOCATION           VALUE ORA_ROWSCN
---------- -------------- ------------- ---------- ----------
        10 ACCOUNTING     NEW YORK               1    4943018
        20 RESEARCH       DALLAS                 1    4943018
        30 SALES          CHICAGO                1    4943018
        40 OPERATIONS     BOSTON                 1    4943018

SQL> update test2 set value=2 where id=20;

已更新 1 行。

SQL> select id,name, location,value,ora_rowscn from test2;

        ID NAME           LOCATION           VALUE ORA_ROWSCN
---------- -------------- ------------- ---------- ----------
        10 ACCOUNTING     NEW YORK               1    4943018
        20 RESEARCH       DALLAS                 2
        30 SALES          CHICAGO                1    4943018
        40 OPERATIONS     BOSTON                 1    4943018

可以看到在沒提交之前,value為2的ORA_ROWSCN已經為空,

SQL> commit;

提交完成。

SQL> select id,name, location,value,ora_rowscn from test2;

        ID NAME           LOCATION           VALUE ORA_ROWSCN
---------- -------------- ------------- ---------- ----------
        10 ACCOUNTING     NEW YORK               1    4943018
        20 RESEARCH       DALLAS                 2    4943097
        30 SALES          CHICAGO                1    4943018
        40 OPERATIONS     BOSTON                 1    4943018

可以看到這次只有id=20的資料的ORA_ROWSCN發生了更新。所以這個時候我們就可以利用ORA_ROWSCN作為樂觀鎖,用不著專門再增加一個額外的域了。而且這個域是Oracle負責維護的,開銷也不是很大,不過需要Oracle 10g才可以使用。

總之,介紹了Oracle的鎖定機制,究竟是悲觀鎖好還是樂觀鎖好,其實也是不一定的。Oracle中悲觀鎖還是很不錯的,而且從開始的時候就把資料鎖定。免除了後面的很多衝突處理。不過悲觀鎖需要保持一個Oracle連線,在我們常見的B/S應用中,特別是資料先取得,然後讓使用者再更新,再返回提交這種流程來說,悲觀鎖是不大可能的。首先是因為B/S應用中,一般是利用一個連線池,在兩次Http Request請求都是不同的資料庫Connection。而且也不能鎖定一個資料太長時間,否則人人都這麼鎖定,應用很容易進入死鎖狀態,這個時候就要採用樂觀鎖了。而在樂觀鎖中,ORA_ROWSCN可以說是比較好的選擇,不過第一需要重新建表,第二需要在Oracle10g下才有這個特性,所以也不是到處都可以使用,當一個萬金油的。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/628922/viewspace-616338/,如需轉載,請註明出處,否則將追究法律責任。

相關文章