Oracle编程入门经典 第12章 事务处理和并发控制
目錄
12.1????????? 什么是事務(wù)處理... 1
12.2????????? 事務(wù)處理控制語句... 1
12.2.1?????? COMMIT處理... 2
12.2.2?????? ROLL BACK處理... 2
12.2.3?????? SAVEPOINT和ROLL BACK TO SAVEPOINT. 3
12.2.4?????? SET TRANSACTION.. 3
試驗:凍結(jié)視圖... 4
12.2.5?????? SET CONSTRAINTS. 5
12.3????????? 事務(wù)處理的ACID屬性... 7
12.3.1?????? 原子性... 7
12.3.2?????? 一致性... 7
試驗:事務(wù)處理級別的一致性... 8
12.3.3?????? 隔離性... 11
12.3.4?????? 持久性... 11
12.4????????? 并發(fā)控制... 11
12.4.1?????? 鎖定... 12
試驗:引發(fā)死鎖... 12
12.4.2?????? 多版本和讀取一致性... 15
考慮一個進(jìn)一步的示例。... 16
12.5????????? 小結(jié)... 17
?
?
?
?
開發(fā)者能夠命名他們的PL/SQL程序塊,為它們確定參數(shù),將它們存儲在數(shù)據(jù)庫中,并且從任何數(shù)據(jù)庫客戶或者實用工具中引用或者運行它們,例如 SQL*Plus、Pro*C,甚至是JDBC。
這此聽PL/SQL程序稱為存儲過程和函數(shù)。它們的集合稱為程序包。在本章中,我們將要解釋使用過程、函數(shù)和程序包的三大優(yōu)勢、這三種相似結(jié)構(gòu)之間的區(qū)別。
?
Oracle 9i產(chǎn)品幫助文檔:
http://docs.oracle.com/cd/B10501_01/index.htm
可根據(jù)自己需要進(jìn)行查詢,包含了眾多的文檔。
?
Sample Schemas的目錄:
http://docs.oracle.com/cd/B10501_01/server.920/a96539/toc.htm
?
Sample Schemas的文檔(示例模式的表及介紹):
http://docs.oracle.com/cd/B10501_01/server.920/a96539.pdf
?
在討論這2個特性的時候,我們將要在本章中學(xué)習(xí)如下內(nèi)容:
●Oracle中的事務(wù)處理是什么
●怎樣控制Oracle中的事務(wù)控制
●Oracle怎樣在數(shù)據(jù)庫中實現(xiàn)并發(fā)控制,讓多個用戶同時訪問和修改相同的數(shù)據(jù)表
?
12.1? 什么是事務(wù)處理
用于有效記錄某機構(gòu)感興趣的業(yè)務(wù)活動(稱為事務(wù))的數(shù)據(jù)處理(例如銷售、供貨的定購或貨幣傳輸)。通常,聯(lián)機事務(wù)處理 (OLTP) 系統(tǒng)執(zhí)行大量的相對較小的事務(wù)。
12.2? 事務(wù)處理控制語句
Oracle中的一個重要概念就是沒有“開始事務(wù)處理”的語句。用戶不能顯式開始一個事務(wù)處理。事務(wù)處理會隱式地開始于第一條修改數(shù)據(jù)的語句,或者一些要求事務(wù)處理的場合。使用COMMIT或者ROLL BACK語句將會顯式終止事務(wù)處理。
如上所述,事務(wù)處理具有原子性,也就是說,或者所有語句都成功執(zhí)行,或者所有語句都不能成功執(zhí)行。我們會在這里介紹其中一些成員:
●COMMIT
●ROLL BACK
●SAVEPOINT
●ROLL BACK TO<SAVEPOINT>
●SET TRANSACTION
●SET CONSTRAINT(S)
?
12.2.1???????????? COMMIT處理
作為開發(fā)者,用戶應(yīng)該使用COMMIT或者ROLL BACK顯式終止用戶的事務(wù)處理,否則用戶正在使用的工具/環(huán)境就將為用戶選取其中一種方式。
無論事務(wù)處理的規(guī)模如何,提交都是非常快速的操作。用戶可能會認(rèn)為事務(wù)處理越大(換句話說,影響的數(shù)據(jù)越多),提交所耗費時間越長。事實并非如此,進(jìn)化論事務(wù)處理規(guī)模如何,提交的響應(yīng)時間通常都很“平緩”。這是因為提交實際上沒有太多的工作去做,但是它所做的工作卻至關(guān)重要。
如果能夠理解這點,那么就可以避免許多開發(fā)者所采用的工作方式,去限制他們的事務(wù)處理規(guī)模,每隔若干行就進(jìn)行提交,而不是當(dāng)邏輯單元的工作已經(jīng)執(zhí)行完畢之后才進(jìn)行提交。他們這樣做是因為他們錯誤地認(rèn)為他們正在降低系統(tǒng)資源的負(fù)載;而事實上,他們正在增加負(fù)載。如果提交一行需要耗費X個時間單元,那么提交1000行也會消耗相同的X個時間單元,而采用1000次提交1行的方式完成這項工作就需要額外運行1000*X個時間單元。如果只在必要的時候進(jìn)行一次提交(當(dāng)事務(wù)處理完成的時候),那么用戶就不僅可以提高性能,而且可以減少對共享資源(日志文件,保護(hù)SGA內(nèi)共享數(shù)據(jù)結(jié)構(gòu)的鎖定)的爭用。
那么,為什么無論事務(wù)處理的規(guī)模如何,提交的響應(yīng)時間都相當(dāng)平緩呢?這是因為在數(shù)據(jù)庫中進(jìn)行提交之前,我們已經(jīng)完成了所有實際工作,我們已經(jīng)修改了數(shù)據(jù)庫中的數(shù)據(jù),完成了99.9%的工作。
當(dāng)提交的時候,我們還要執(zhí)行三個任務(wù):
●為我們的事務(wù)處理生成 SCN(系統(tǒng)改變編號)。這是Oracle的內(nèi)部時鐘,可以稱為數(shù)據(jù)庫時間。SCN不是傳統(tǒng)意義上的時鐘,因為它不是隨著時間失衡而遞進(jìn)。相反,它是在事務(wù)處理提交的時候遞進(jìn),由Oracle在內(nèi)部使用,以對事務(wù)處理排序。
●將所有剩余的已經(jīng)緩沖的重做日志表項寫入磁盤,并且將SCN記錄到在重做日志文件中。這要由LGWR執(zhí)行。
●釋放我們的會話(以及所有正在等等我們所占有鎖定的用戶)所占有的所有鎖定。
LGWR不會一直緩沖完成的所有工作,而是會隨著操作的進(jìn)行,在后臺不斷清理重做日志緩沖的內(nèi)容,這非常類似于在PC機上運行的磁盤緩沖軟件。它可以避免在清理所有的用戶重做日志時,提交操作出現(xiàn)長時間等待現(xiàn)象。LGWR會在以下情況執(zhí)行清理工作:
●每隔3秒
●當(dāng)SGA中的日志緩沖超過了1/3的空間,或者包含了1MB或者更多的已緩沖數(shù)據(jù)
●進(jìn)行任何事務(wù)處理提交
所以,即使我們長時間運行事務(wù)處理,也會有部分它所產(chǎn)生的已緩沖重做日志在提交之前寫入了磁盤。
?
12.2.2???????????? ROLL BACK處理
當(dāng)我們進(jìn)行回滾的時候,我們還有一些任務(wù)需要執(zhí)行:
●撤銷所有已經(jīng)執(zhí)行的改變。這要通過讀取我們生成的UNDO數(shù)據(jù),有效地反轉(zhuǎn)我們的操作來完成。如果我們插入了一行,那么回滾就要刪除它。如果我們更新了一行,回滾就要將其更新到原來的樣子。如果我們刪除了一行,就要重新插入它。
●釋放我們的會話(以及正在等等我們已經(jīng)鎖定的行的用戶)占用的所有鎖定。
?
12.2.3???????????? SAVEPOINT和ROLL BACK TO SAVEPOINT
SAVEPOINT可以讓用戶在事務(wù)處理中建立標(biāo)記點。用戶可以在單獨的事務(wù)處理中擁有多個保存點。當(dāng)使用ROLL BACK TO <SAVEPOINT NAME>的時候,它就可以讓用戶有選擇地回滾更大的事務(wù)處理中的一組語句。
保存點是委有用的事務(wù)處理特性,它們可以讓用戶將單獨的大規(guī)模事務(wù)處理分割成一系列較小的部分。擬執(zhí)行的所有語句仍然是較大的事務(wù)處理的組成部分,用戶可以將特定的語句組織到一起,將它們作為單獨的語句進(jìn)行回滾。
為了做到這一點,用戶需要在開始函數(shù)的時候使用如下的SQL語句:
Savepoint function_name;?
這里的function_name是用戶的函數(shù)名稱。如果用戶在處理期間遇到錯誤,用戶就只需使用:
Roll back to function_name;?
?
12.2.4???????????? SET TRANSACTION
這個語句可以使您設(shè)置事務(wù)處理的各種屬性,例如它的隔離層(參考以下的隔離層次列表),它是只讀還是可以進(jìn)行讀寫,以及是否要使用特定的回滾段。
SET TRANSACTION語句必須是事務(wù)處理中使用的第一個語句。這就是說,必須在任何INSERT、UPDATE或者DELETE語句,以及任何其它可以開始事務(wù)處理的語句之前使用它。SET TRANSACTION的作用域只是當(dāng)前的事務(wù)處理。
SET TRANSACTION語句可以讓用戶:
●規(guī)定事務(wù)處理隔離層次。
●規(guī)定為用戶事務(wù)處理所使用的特定回滾段。
●命名用戶事務(wù)處理。
?
我們將要在這里使用的重要SET TRANSACTION語句包括:
●SET TRANSACTION READ ONLY
●SET TRANSACTION READ WRITE
●SET TRANSACTION ISOLACTION LEVEL SERIALIZABLE
●SET TRANSACTION ISOLACTION LEVEL READ COMMITTED
注意:
SET TRANSACTION語句事實上都是互斥的。例如,如果您選擇了READ ONLY,那么就不能為它選擇READ WRITE、SERIALIZABLE或者READ COMMITTED。
?
命令SET TRANSACTION READ ONLY將會做2件事,它會確保您無法執(zhí)行修改數(shù)據(jù)的DML操作,例如INSERT、UPDATE或者DELETE。如下所示:
SQL> set transaction read only;事務(wù)處理集。SQL> update emp set ename=lower(ename); update emp set ename=lower(ename)* ERROR 位于第 1 行: ORA-01456: 不可以在 READ ONLY 事務(wù)處理中執(zhí)行插入/刪除/更新操作?
?
其效果很明顯。而READ ONLY事務(wù)處理的另一個副作用則更為微妙。通過將事務(wù)處理設(shè)置為READ ONLY,我們就可以有效地將我們的數(shù)據(jù)庫視圖凍結(jié)到某個時間點。我的意思是,無論數(shù)據(jù)庫中的其它會話如何工作,數(shù)據(jù)庫在我們的面前都會是使用SET TRANSACTION語句時候樣子。這有什么用處呢?我們假定現(xiàn)在是一個繁忙的工作日的下午1點。用戶需要運行一個報表。這個報表將要執(zhí)行幾十條查詢,從許多數(shù)據(jù)源中獲取數(shù)據(jù)。所有這些數(shù)據(jù)都會相互關(guān)聯(lián),為了使其有意義,它們必須保持一致。換句話說,用戶需要每個查詢都看到“1點鐘”的數(shù)據(jù)庫。如果每個查詢看不同時間點的數(shù)據(jù)庫,那么我們報告中的結(jié)果就沒有意義。
為了確保用戶數(shù)據(jù)一致,用戶應(yīng)該鎖定報表查詢所需的所有表,防止其它用戶更新它們。作為一種替代的方法,用戶可以使用SET TRANSACTION READ ONLY語句,凍結(jié)“1點鐘”的用戶數(shù)據(jù)庫視圖。這可以得到兩全其美的結(jié)果。
試驗:凍結(jié)視圖
(1)?????? 為了運行這個示例,用戶需要在SQL*Plus中打開3個會話。建立一個表。
SQL> create table t as select object_id from all_objects where rownum<=2000; 表已創(chuàng)建。?
(2)?????? 用以觀察READ ONLY和READ WRITE事務(wù)處理區(qū)別。
| 時間 | 會話1 | 會話2 | 會話3 | 注釋 |
| T1 | set transaction read only; | ? | ? | ? |
| T2 | select count(*) from t; | select count(*) from t; | ? | 2個事務(wù)處理都可以看到2000行 |
| T3 | ? | ? | delete from t where rownum<=500; | 從T中刪除500行,但是沒有提交 |
| T4 | select count(*) from t; | select count(*) from t; | ? | 由于多版本的作用,所以這2個會話都會看到2000行 |
| T5 | ? | ? | commit; | 讓500個已刪除的行永久刪除 |
| T6 | select count(*) from t; | select count(*) from t; | ? | 會話1仍然會看到2000行,會話2現(xiàn)在將要看到1500行!到提交或者回滾為止,會話1將會一直看到200行 |
| T7 | ? | ? | insert into t select * from t; | 對T的規(guī)模進(jìn)行加倍,使其達(dá)到3000個記錄 |
| T8 | ? | ? | commit; | 使得改變可見 |
| T9 | select count(*) from t; | select count(*) from t; | ? | 會話1會繼續(xù)看到2000行。會話2現(xiàn)在可以看到所有的3000行 |
| T10 | commit; | ? | ? | ? |
| T11 | select count(*) from t; | ? | ? | 會話1現(xiàn)在也可以看到3000行。 |
?
第二個命令SET TRANSACTION READ WRITE不會經(jīng)常使用,因為它是默認(rèn)設(shè)置。很少有必要使用這個命令,在這里包含它只是出于完整性的考慮。
?
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE與READ ONLY有一些類似。當(dāng)使用了這個命令之后,無論是否出現(xiàn)改變,數(shù)據(jù)庫都會為您進(jìn)行“凍結(jié)”。就如同我們在READ ONLY事務(wù)處理中看到的那樣,您可以完全隔離其它事務(wù)處理的影響。
這個命令大體相當(dāng)于將事務(wù)處理設(shè)置為READ WRITE,由于它是設(shè)置隔離層次時Oracle的默認(rèn)操作模式,所以很少使用。如果您在會話的前面使用ALTER SESSION命令,將用戶會話的事務(wù)處理的默認(rèn)隔離層次從READ COMMITTED改變?yōu)镾ERIABLIZABLE,那么就可以會用到這個命令。使用ISOLATION LEVEL READ COMMITTED命令可以重置默認(rèn)值。
12.2.5???????????? SET CONSTRAINTS
在Oracle中,約束可以在DML語句執(zhí)行后立即生效,也可以延遲到事務(wù)處理提交的時候才生效。SET CONSTRAINT語句可以讓您在事務(wù)處理中設(shè)置延遲約束的強制模式。可以使用如下語法延遲單獨的約束:
set constraint constraint_name defferred?
或者,您也可以使用如下語法延遲所有約束:
set constraints all defferred?
在這些命令中,您可以使用關(guān)鍵字IMMEDIATE代替DEFERRED,將他們的強制模式改回立即模式。為了看到實際的命令,我們可以使用一個簡單的示例:
SQL> drop table t;表已丟棄。SQL> create table t2 (x int,3 constraint x_greater_than_zero check(x>0)4 deferrable initially immediate5 )6 /表已創(chuàng)建。SQL> insert into t values(-1); insert into t values(-1) * ERROR 位于第 1 行: ORA-02290: 違反檢查約束條件 (SCOTT.X_GREATER_THAN_ZERO)?
不錯,我們無法向X中插入值-1.現(xiàn)在,我們使用SET CONSTRAINT命令延遲約束的臉證,如下所示:
SQL> set constraint x_greater_than_zero deferred; 約束條件已設(shè)置。SQL> insert into t values(-1); 已創(chuàng)建 1 行。?
然而,我們無法在數(shù)據(jù)庫中提交這條語句:
SQL> commit; commit * ERROR 位于第 1 行: ORA-02091: 事務(wù)處理已重算 ORA-02290: 違反檢查約束條件 (SCOTT.X_GREATER_THAN_ZERO)?
有2種方法使用這個命令,或者規(guī)定一組約束名稱,或者使用關(guān)鍵詞ALL。例如,為了延遲約束X_GREATER_THAN_ZERO,我們可以使用:
set constraint x_greater_than_zero deferred;?
我們也可以很容易地使用如下命令:
set constraints all deferred;?
這里的區(qū)別是第2個版本使用了ALL,它會影響我們會話中的所有延遲約束,而不只是我們感興趣的約束。而且,為了明確使用用戶約束,也可以使用如下語句:
set constraint <constraint_name> immediate;?
例如,我們可以在以上的救命中使用如下語句:
SQL> set constraint x_greater_than_zero deferred;約束條件已設(shè)置。SQL> insert into t values(-1);已創(chuàng)建 1 行。SQL> set constraint x_greater_than_zero immediate; SET constraint x_greater_than_zero immediate * ERROR 位于第 1 行: ORA-02290: 違反檢查約束條件 (SCOTT.X_GREATER_THAN_ZERO)SQL> commit; commit * ERROR 位于第 1 行: ORA-02091: 事務(wù)處理已重算 ORA-02290: 違反檢查約束條件 (SCOTT.X_GREATER_THAN_ZERO)?
Oracle會回滾我們的事務(wù)處理。通過SET CONSTRAINT <constraint_name> IMMEDIATE命令,我們就能夠發(fā)現(xiàn)我們已經(jīng)違反了一些約束,而我們的事務(wù)處理仍然有效。這個約束將會處理延遲模式,它不會立即檢查。
12.3? 事務(wù)處理的ACID屬性
ACID是替代如下內(nèi)容的首字母縮寫:
●原子性(Atomicity)——事務(wù)處理要么全部進(jìn)行,要么不進(jìn)行。
●一致性(Consistency)——事務(wù)處理要將數(shù)據(jù)庫從一種狀態(tài)變成另一種狀態(tài)。
●隔離性(Isolation)——在事務(wù)處理提交之前,事務(wù)處理的效果不能由系統(tǒng)中其它事務(wù)處理看到。
●持久性(Durability)——一旦提交了事務(wù)處理,它就永久生效。
?
12.3.1???????????? 原子性
在Oracle中,事務(wù)處理具有原子性。換句話說,或者提交所有的工作,或者什么工作都不提交。
12.3.2???????????? 一致性
這是非常重要的事務(wù)處理特性,任何事務(wù)處理都會將數(shù)據(jù)庫從一種邏輯上的一致狀態(tài)轉(zhuǎn)變?yōu)榱硪环N邏輯上的一致狀態(tài)。這就是說,在事務(wù)處理開始之前,數(shù)據(jù)庫的所有數(shù)據(jù)都會滿足您已經(jīng)施加給數(shù)據(jù)庫的業(yè)務(wù)規(guī)則(約束)。
我們所使用的表和觸發(fā)器如下所示:
SQL> drop table t;表已丟棄。SQL> create table t2 ( x int,3 constraint t_pk primary key(x)4 )5 /表已創(chuàng)建。SQL> create trigger t_trigger2 after update on T for each row3 begin4 dbms_output.put_line('Updated x='||:old.x||' to x='||:new.x);5 end;6 /觸發(fā)器已創(chuàng)建?
現(xiàn)在,我們要向T中插入一些行:
SQL> insert into t values(1);已創(chuàng)建 1 行。SQL> insert into t values(2);已創(chuàng)建 1 行。?
這就結(jié)束了這個示例的設(shè)置。現(xiàn)在,物體 嘗試對表T進(jìn)行更新,將所有行都設(shè)置為數(shù)值2。
SQL> set serverout on SQL> begin2 update t set x=2;3 end;4 / Updated x=1 to x=2 Updated x=2 to x=2 begin * ERROR 位于第 1 行: ORA-00001: 違反唯一約束條件 (SCOTT.T_PK) ORA-06512: 在line 2?
現(xiàn)在,如果我們使用如下所示的成功語句:
SQL> begin2 update t set x=x+1;3 end;4 / Updated x=1 to x=2 Updated x=2 to x=3PL/SQL 過程已成功完成。?
數(shù)據(jù)庫在邏輯上保持了一致(它滿足了所有的業(yè)務(wù)規(guī)則),所以語句將會成功。
試驗:事務(wù)處理級別的一致性
(1)?????? 我們要建立2個表PARENT和CHILD。CHILD表具有PARENT表上的外鍵,這個外鍵要定義為可延遲。
SQL> create table parent(pk int,2 constraint parent_pk primary key(pk));表已創(chuàng)建。SQL> create table child(fk,2 constraint child_fk foreign key(fk)3 references parent deferrable);表已創(chuàng)建。?
(2)?????? 現(xiàn)在,我們使用一些數(shù)據(jù)生成這些表。
SQL> insert into parent values(1);已創(chuàng)建 1 行。SQL> insert into child values(1);已創(chuàng)建 1 行。?
(3)?????? 現(xiàn)在,我們嘗試更新PARENT表,改變它的主鍵值。
SQL> update parent set pk=2; update parent set pk=2 * ERROR 位于第 1 行: ORA-02292: 違反完整約束條件 (SCOTT.CHILD_FK) - 已找到子記錄日志?
(4)?????? 為了解決這個問題,我們只需告訴Oracle我們將要延遲約束CHILD_FK,也就是說,我們不想它在語句層次進(jìn)行檢查。
SQL> set constraints child_fk deferred; 約束條件已設(shè)置。?
(5)?????? 現(xiàn)在,我們可以正確地更新PARENT表
SQL> update parent set pk=2; 已更新 1 行。SQL> select * from parent;PK ----------2SQL> select * from child;FK ----------1 SQL> commit; commit * ERROR 位于第 1 行: ORA-02091: 事務(wù)處理已重算 ORA-02292: 違反完整約束條件 (SCOTT.CHILD_FK) - 已找到子記錄日志?
(6)?????? 我們再次進(jìn)行嘗試,看看如何讓Oracle驗證我們數(shù)據(jù)的邏輯一致性,而不進(jìn)行回滾。我們首先會再次設(shè)置約束DEFERRED,然后再次更新父表,重做Oracle剛才撤銷的工作。
SQL> set constraints child_fk deferred; 約束條件已設(shè)置。SQL> update parent set pk=2; 已更新 1 行。SQL> select * from parent;PK ----------2SQL> select * from child;FK ----------1?
(7)?????? 我們現(xiàn)在將CHILD_FK約束設(shè)置為IMMEDIATE。這將導(dǎo)致Oracle立即驗證我們業(yè)務(wù)規(guī)則的一致性。
SQL> set constraints child_fk immediate; SET constraints child_fk immediate * ERROR 位于第 1 行: ORA-02291: 違反完整約束條件 (SCOTT.CHILD_FK) - 未找到父項關(guān)鍵字?
(8)?????? 現(xiàn)在,我們可以更新CHILD記錄,再次檢查約束并且提交
SQL> update child set fk=2; 已更新 1 行。SQL> set constraints child_fk immediate; 約束條件已設(shè)置。SQL> commit; 提交完成。 SQL> select * from parent;PK ----------2SQL> select * from child;FK ----------2?
這將會導(dǎo)致一致性數(shù)據(jù)滿足我們的約束。
12.3.3???????????? 隔離性
在給定用戶隔離層次的情況下,使用相同的輸入,采用相同的方式執(zhí)行的相同的工作可能會導(dǎo)致不同的答案,這些隔離層次采用指定層次上許可(或者不符合規(guī)定)的三種“讀取”方式進(jìn)行定義。它們是:
●臟讀取(Dirty read)——這種讀取方式的含義同它聽起來一樣糟糕。您可以讀取沒有提交的“臟”數(shù)據(jù)。
●非可重復(fù)讀取(Non-repeatable read)——這種方式意味著,如果用戶在T1時刻讀取了一行,在T2時刻再次讀取一行,那么這個行就可能發(fā)生改變。例如,它可能已經(jīng)被刪除或者更新。
●影像讀取(Phantom read)——這種方式意味著如果用戶在T1時刻執(zhí)行了一個查詢,在T2時刻再次執(zhí)行它,就可能會有影響結(jié)果的附加行加入到數(shù)據(jù)庫中。在這種情況下,這種讀取方式與非可重復(fù)讀取有所不同,您已經(jīng)讀取的數(shù)據(jù)不會發(fā)生變化,而是會有比以前更多的滿足查詢條件的數(shù)據(jù)。
SQL92采用了這三種“讀取”方式,并且基于它們的存在與否建立了4種隔離層次,見表13-2,它們是:
| 隔離層次 | 臟讀取 | 非重復(fù)讀取 | 影像讀取 |
| 非提交讀取(Read Uncommitted) | 允許 | 允許 | 允許 |
| 提交讀取(Read committed) | 禁止 | 允許 | 允許 |
| 可重復(fù)讀取(Repeatable Read) | 禁止 | 禁止 | 允許 |
| 串行讀取(Serializable) | 禁止 | 禁止 | 禁止 |
?
12.3.4???????????? 持久性
持久性是數(shù)據(jù)庫提供的最重要的特性之一。它可以確保一旦事務(wù)處理提交之后,它的改變就會永久生效。它們不會由于系統(tǒng)故障或者錯誤而“消失”。
?
12.4? 并發(fā)控制
開發(fā)多用戶、數(shù)據(jù)庫驅(qū)動的應(yīng)用 的主要挑戰(zhàn)之一就是要最大化并發(fā)訪問(多個用戶同時訪問數(shù)據(jù)),而且與此同時,還要確保每個用戶都能在一致的方式下讀取和修改數(shù)據(jù)。能夠提供這些功能的鎖定和并發(fā)控制是所有數(shù)據(jù)庫的關(guān)鍵特性。
12.4.1???????????? 鎖定
鎖定(lock)是用來控制共享資源并發(fā)訪問的機制。要注意,我們使用術(shù)語“共享資源”,而沒有使用“數(shù)據(jù)庫行”或者“數(shù)據(jù)庫表”。Oracle確實可以在行級別上鎖定表數(shù)據(jù),但是它還可以在許多不同的層次上使用鎖定,提供對各種資源的并發(fā)訪問。
大多數(shù)情況下,鎖對于作為開發(fā)者的用戶來講是透明的。當(dāng)更新數(shù)據(jù)行的時候,我們不必對其進(jìn)行鎖定,Oracle會為我們完成這些工作。有些時候顯式鎖定是必須的,但是大多數(shù)時候,鎖定會自行管理。
在單用戶的數(shù)據(jù)庫中,鎖根本沒有必要。因為根據(jù)定義,在這種情況下只有一個用戶會修改信息。然而,當(dāng)多用戶訪問或者修改數(shù)據(jù)或者數(shù)據(jù)結(jié)構(gòu)的時候,具有可以防止并發(fā)訪問修改相同信息的機制就分外重要。這就是鎖定的價值。
當(dāng)2個用戶戰(zhàn)勝了2者都希望使用的資源時就會出現(xiàn)死鎖。
Oracle處理死鎖的方式非常簡單。當(dāng)檢測出死鎖的時候(它們就會立刻被檢測出來),Oracle就會選擇一個會話作為“犧牲者”。
試驗:引發(fā)死鎖
(1)?????? 我們要建立2個所需要的表,如下所示:
SQL> create table a as select 1 x from dual; 表已創(chuàng)建。SQL> create table b as select 1 x from dual; 表已創(chuàng)建。?
(2)?????? 現(xiàn)在,打開SQL*Plus會話,并且更新表A。
SQL> update a set x=x+1;已更新 1 行。?
(3)?????? 新打開一個SQL*Plus會話,更新B表。
SQL> update b set x=x+1;已更新 1 行。?
(4)?????? 在相同的會話,再一次更新a表
SQL> update a set x=x+1;?
會發(fā)生死鎖。
可以在許多層次上進(jìn)行鎖定。用戶可以在一行上擁有鎖定,或者實際上也可以在一個表上擁有鎖定。一些數(shù)據(jù)庫(盡管沒有Oracle)還可以在數(shù)據(jù)庫層次上鎖定數(shù)據(jù)。在一些數(shù)據(jù)庫中,鎖定是稀缺的資源,擁有許多鎖定可以負(fù)面地影響系統(tǒng)的性能。在這些數(shù)據(jù)庫中,你可以發(fā)現(xiàn)為了保存這些資源,用戶的100個行級別的鎖定會轉(zhuǎn)換為一個表級別的鎖定。這個過程就稱為鎖定升級(lock escalation)。它使得系統(tǒng)降低了用戶鎖定的粒度。
鎖定升級不是數(shù)據(jù)庫想要的屬性。事實上,數(shù)據(jù)庫支持升級鎖定暗示著它的鎖定機制存在固有的系統(tǒng)開銷,管理上百個鎖定是需要處理的重要工作。在Oracle中,擁有一個鎖定或者上百萬個鎖定的系統(tǒng)開銷是相同的——沒有區(qū)別。
Oracle會使用鎖定轉(zhuǎn)換(lock conversion)或者提升(promotion)。它將會在盡可能級別上獲取鎖定,并且將鎖定轉(zhuǎn)換到更具限制性的級別上。例如,如果使用FOR UPDATE子句從表中選取一行,那么就會應(yīng)用2個鎖定。一個鎖定會放置在您所選擇的行上。這個鎖定是的,它將會阻止其它的用戶鎖定指定行。另一個鎖定要放置在表上,它是一個ROW SHARE TABLE鎖。這個鎖將會阻止其它會話獲取表上的排它鎖。
阻塞(Blocking)會在一個會話擁有另一個會話正在請求的資源上的鎖定時出現(xiàn)。正在進(jìn)行請示的會話一直阻塞,直到占用資源的會話翻譯鎖定資源為止。例如,USER1、USER2同時查詢,1分鐘后,USER1修改了USER3的地址,USER2的會話仍然為1分鐘前的數(shù)據(jù),修改了USER3的電話號碼,此時,USER2的舊數(shù)據(jù)替換了USER1修改的數(shù)據(jù)。
悲觀鎖定(Pessimistic locking)聽起來不好,但是相信我,它實際并非如此。在使用悲觀鎖定的時候,您就是在表明“我相信,某人很有可能會改變我正在讀取(并且最終要更新)的相同數(shù)據(jù),因此,在我們花費時間,改變數(shù)據(jù)之前,我要鎖定數(shù)據(jù)庫中的行,防止其它會話更新它”。
為了完成這項工作,我們要使用類似如下語句的查詢:
select * from table where column1 = : old_column1and column2 = : old.column2and...and primary_key = : old_primary_keyfor update nowait;?
所以我們將會從這個語句中得到三種結(jié)果:
●我們將要獲取我們的行,并且對這個行進(jìn)行鎖定,防止被其它會話更新(但是阻止讀取)。
●我們將會得到ORA-00054 Resource Busy錯誤。其它的人已經(jīng)鎖定了這個行,我們必須要等待它。
●由于某人已經(jīng)對行進(jìn)行了改變,所以我們將會返回0行。
由于我們要在進(jìn)行更新之前對行進(jìn)行鎖定,所以這稱為悲觀鎖定(pessimistic locking)。我們對行維持不被改動并不樂觀(因此命名為悲觀)。用戶應(yīng)用中的活動流程應(yīng)該如下所示:
●不進(jìn)行鎖定查詢數(shù)據(jù)
SQL> select empno,ename,sal from emp where deptno=10;EMPNO ENAME SAL ---------- ---------- ----------7782 CLARK 24507839 KING 50007934 MILLER 1300?
●允許終端用戶“查詢”數(shù)據(jù)。
SQL> select empno,ename,sal2 from emp3 where empno=79344 and ename='MILLER'5 and sal=13006 for update nowait7 /EMPNO ENAME SAL ---------- ---------- ----------7934 MILLER 1300?
●假如我們鎖定了數(shù)據(jù)行,我們的應(yīng)用就可以使用更新,提交改變
SQL> update emp2 set ename='miller'3 where empno=7934; 已更新 1 行。SQL> commit; 提交完成。?
在Oracle中,悲觀鎖定可以運行良好。它可以讓用戶相信他們正在屏幕上修改的數(shù)據(jù)當(dāng)前由他們所“擁有”。如果用戶要離開,或者一段時間沒有使用記錄,用戶就要讓應(yīng)用釋放鎖定,或者也可以在數(shù)據(jù)庫中使用資源描述文件設(shè)定空閑會話超時。這樣就可以避免用戶鎖定數(shù)據(jù)行,然后回家過夜的問題。
第二種方法稱為樂觀鎖定(optimistic locking),這經(jīng)會在應(yīng)用中同時保存舊值和新值。
我們首先作為用戶SCOTT登錄,并且打開2個SQL*Plus會話,我們要作為2個獨立的用戶(USER1和USER2)。然后,2個用戶都要使用以下的SELECT語句。
USER1
SQL> select * from dept where deptno=10;DEPTNO DNAME LOC ---------- -------------- -------------10 ACCOUNTING NEW YORK?
USER1更新
SQL> update dept2 set loc='BOSTON'3 where deptno=10; 已更新 1 行。?
USER2更新
SQL> update dept2 set loc='ALBANY'3 where deptno=10; 已更新 1 行。?
DEPTNO=10的LOC=’BOSTON’的記錄不再存在,所以這里更新了0行。我們樂觀地認(rèn)為更新可以完成。
悲觀鎖定最大的優(yōu)勢在于終端用戶可以在他們花費時間進(jìn)行修改之前,就能夠發(fā)現(xiàn)他們的改變不能夠進(jìn)行。而在樂觀鎖定的情況中,終端用戶將會花費大量的時間進(jìn)行修改。
12.4.2???????????? 多版本和讀取一致性
Oracle使用了多版本讀取一致性并發(fā)模型。從根本講,Oracle通過這個機制可以提供:
●讀取一致性查詢(Read-consistent queries):能夠產(chǎn)生結(jié)果與相應(yīng)時間點一致的查詢。
●非阻塞查詢(Non-blocking queries):查詢不會被數(shù)據(jù)定入器阻塞。
SQL> drop table t;表已丟棄。SQL> create table t as select * from all_users;表已創(chuàng)建。SQL> set serverout on SQL> declare2 cursor c1 is select username from t;3 l_username varchar2(30);4 begin5 open c1;6 delete from t;7 commit;8 loop 9 fetch c1 into l_username;10 exit when c1%notfound;11 dbms_output.put_line(l_username);12 end loop;13 close c1;14 end;15 / SYS SYSTEM OUTLN DBSNMP WMSYS ORDSYS ORDPLUGINS MDSYS ……PL/SQL 過程已成功完成。?
雖然從表中刪除所有數(shù)據(jù),我們甚至可以提交刪除工作。這些行都不見了,我們也會這樣認(rèn)為。事實上,它們還可以通過游標(biāo)獲得。實際上,通過OPEN命令返回給我們的結(jié)果集在打開它的時候就已經(jīng)預(yù)先確定了。我們在獲取數(shù)據(jù)之前沒有辦法知道答案是什么,但是從游標(biāo)的角度來看,結(jié)果是不變的。并不是Oracle在我們打開游標(biāo)的時候,將所有以上數(shù)據(jù)復(fù)制到了其它的位置;事實上是刪除操作作為我們保留了數(shù)據(jù),將它們放置到了UNDO表空間或者回滾段中。
考慮一個進(jìn)一步的示例。
建立一個ACCOUNTS的表。可以使用如下方式建立它:
SQL> create table accounts2 (3 account_id number,4 account_type varchar2(20),5 balance number6 );表已創(chuàng)建。?
精確快速地報告BALANCE總數(shù)。用戶需要報告時間以及所有賬戶余額總和:
SQL> select current_timestamp,sum(balance) total_balance from accounts;?
注意:
CURRENT_TIMESTAMP是Oracle 9i的內(nèi)建列,它將會返回當(dāng)前的日期和時間。在較早的Oracle版本中,用戶需要使用SYSDATE。
然而,如果當(dāng)我們進(jìn)行處理的時候,這個數(shù)據(jù)庫表上要應(yīng)用成百上千個事務(wù)處理,那么問題就會稍微復(fù)雜一些。人們會從他們的儲蓄賬號向支票賬號劃轉(zhuǎn)資金,他們還要進(jìn)行取款、存款(我們可能是一個非常繁忙的銀行)等。
我們假定ACCOUNTS表如表13-3所示:
表13-3 ACCOUNTS表示例
| ACCOUNT_ID | ACCOUNT_TYPE | BALANCE |
| 1234 | 儲蓄 | 100 |
| 5678 | 支票 | 4371 |
| 2542 | 儲蓄 | 6232 |
| 7653 | 儲蓄 | 234 |
| …<上百萬行> | ? | ? |
| 1234 | 支票 | 100 |
我們的示例將要有2個結(jié)局,一個結(jié)局將要展示當(dāng)查詢檢測到鎖定靈氣的時候會出現(xiàn)什么情況,另一個結(jié)局展示當(dāng)查詢檢測到數(shù)據(jù)已經(jīng)發(fā)生改變,它不能夠看到它們的時候會出現(xiàn)什么情況。
表13-4 查詢遇到鎖定數(shù)據(jù)
| 時間 | 事件 | 注釋 |
| T1 | 我們在會話1中開始查詢W(現(xiàn)在有150) | 它要開始對表進(jìn)行讀取,由于ACCOUNTS表非常大,所以這 需要花幾分鐘的時間。這個查詢已經(jīng)讀取了“第一行”的ACCOUNT_ID為1234儲蓄賬號,但是還沒有到達(dá)支票賬號 |
| T2 | 賬號1234的所有者在ATM上開始事務(wù)處理 | ? |
| T3 | 賬號1234的所有者選取TRANSFERFUNDS,并且選取從他們的支票賬號向儲蓄賬號劃轉(zhuǎn)50美金(150-50) | 數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行了更新,所以支票賬號現(xiàn)在具有50美金,而儲蓄賬號具有150美金。工作還沒有提交(但是已經(jīng)存儲了UNDO信息) |
| T4 | 我們查詢最終到達(dá)ACCOUNT_ID為1234的支票賬戶行 | 這時發(fā)生什么呢?在其它大多數(shù)流行數(shù)據(jù)庫中,答案是“查詢將要等待”。而Oracle中不會這樣 |
| T5 | 由于檢測到數(shù)據(jù)已經(jīng)被T3時刻執(zhí)行的工作所鎖定,所以我們的查詢將要接受到UNDO信息(數(shù)據(jù)“以前的”映像),并且使用T1時刻的數(shù)據(jù)映像 | Oracle將要講到鎖定,它不會等待。我們查詢將要在支票賬號讀取到100美金 |
| T6 | 我們的報告生成 | ? |
| T7 | ATM提交并且完成 | ? |
本將操作的有意思的部分發(fā)生在以上時間鏈T5時刻
表13-5 查詢遇到已經(jīng)改變的數(shù)據(jù)
| 時間 | 事件 | 注釋 |
| T4 | ATM會話進(jìn)行提交,完成轉(zhuǎn)賬 | 資金發(fā)生轉(zhuǎn)移,另外的會話現(xiàn)在可以看到在ACCOUNT_ID為1234的儲蓄賬號中有150美金,支票賬號中有50美金 |
| T5 | 我們查詢最終到達(dá)ACCOUNT_ID為1234的支票賬戶行 | 它不再鎖定,沒有人正在更新它 |
| T6 | 由于檢測到在T1時刻之后數(shù)據(jù)已經(jīng)進(jìn)行了修改,我們的查詢將會接收到UNDO信息,并且使用T1時刻的數(shù)據(jù)映像 | 我的查詢將要再次為支票賬號讀取100美金 |
| T6 | 我們的報告生成 | ? |
簡單來說,Oracle除了刪除,在更新、增加中,能夠把UNDO表空間或者回滾段中的數(shù)據(jù)讀取出來,來為客戶展示它數(shù)據(jù)的一致性。
12.5? 小結(jié)
在本章中,我們首先了事務(wù)處理的構(gòu)成。了解了事務(wù)處理控制語句,以及怎樣和在什么時候使用它們。
當(dāng)討論并發(fā)控制的時候,我們討論了2個重要而又復(fù)雜的主題,它們是鎖定和多版本讀取一致性。了解了死鎖、跟蹤文件分析死鎖、數(shù)據(jù)庫中避免阻塞等待的不同模式、悲觀鎖定和樂觀鎖定。
?
文章根據(jù)自己理解濃縮,僅供參考。
摘自:《Oracle編程入門經(jīng)典》 清華大學(xué)出版社?http://www.tup.com.cn
from:?http://www.cnblogs.com/yongfeng/p/3219593.html
總結(jié)
以上是生活随笔為你收集整理的Oracle编程入门经典 第12章 事务处理和并发控制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Oracle编程入门经典 第11章 过程
- 下一篇: 浅淡Webservice、WSDL三种服