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