目錄數據庫
12.1 什麼是事務處理... 1編程
12.2 事務處理控制語句... 1數據結構
12.2.1 COMMIT處理... 2併發
12.2.2 ROLL BACK處理... 2oracle
12.2.3 SAVEPOINT和ROLL BACK TO SAVEPOINT. 3函數
12.2.4 SET TRANSACTION.. 3工具
試驗:凍結視圖... 4oop
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怎樣在數據庫中實現併發控制,讓多個用戶同時訪問和修改相同的數據表
用於有效記錄某機構感興趣的業務活動(稱爲事務)的數據處理(例如銷售、供貨的定購或貨幣傳輸)。一般,聯機事務處理 (OLTP) 系統執行大量的相對較小的事務。
Oracle中的一個重要概念就是沒有「開始事務處理」的語句。用戶不能顯式開始一個事務處理。事務處理會隱式地開始於第一條修改數據的語句,或者一些要求事務處理的場合。使用COMMIT或者ROLL BACK語句將會顯式終止事務處理。
如上所述,事務處理具備原子性,也就是說,或者全部語句都成功執行,或者全部語句都不能成功執行。咱們會在這裏介紹其中一些成員:
●COMMIT
●ROLL BACK
●SAVEPOINT
●ROLL BACK TO<SAVEPOINT>
●SET TRANSACTION
●SET CONSTRAINT(S)
做爲開發者,用戶應該使用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或者更多的已緩衝數據
●進行任何事務處理提交
因此,即便咱們長時間運行事務處理,也會有部分它所產生的已緩衝重作日誌在提交以前寫入了磁盤。
當咱們進行回滾的時候,咱們還有一些任務須要執行:
●撤銷全部已經執行的改變。這要經過讀取咱們生成的UNDO數據,有效地反轉咱們的操做來完成。若是咱們插入了一行,那麼回滾就要刪除它。若是咱們更新了一行,回滾就要將其更新到原來的樣子。若是咱們刪除了一行,就要從新插入它。
●釋放咱們的會話(以及正在等等咱們已經鎖定的行的用戶)佔用的全部鎖定。
SAVEPOINT可讓用戶在事務處理中創建標記點。用戶能夠在單獨的事務處理中擁有多個保存點。當使用ROLL BACK TO <SAVEPOINT NAME>的時候,它就可讓用戶有選擇地回滾更大的事務處理中的一組語句。
保存點是委有用的事務處理特性,它們可讓用戶將單獨的大規模事務處理分割成一系列較小的部分。擬執行的全部語句仍然是較大的事務處理的組成部分,用戶能夠將特定的語句組織到一塊兒,將它們做爲單獨的語句進行回滾。
爲了作到這一點,用戶須要在開始函數的時候使用以下的SQL語句:
Savepoint function_name;
這裏的function_name是用戶的函數名稱。若是用戶在處理期間遇到錯誤,用戶就只需使用:
Roll back to function_name;
這個語句可使您設置事務處理的各類屬性,例如它的隔離層(參考如下的隔離層次列表),它是隻讀仍是能夠進行讀寫,以及是否要使用特定的回滾段。
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命令能夠重置默認值。
在Oracle中,約束能夠在DML語句執行後當即生效,也能夠延遲到事務處理提交的時候才生效。SET CONSTRAINT語句可讓您在事務處理中設置延遲約束的強制模式。可使用以下語法延遲單獨的約束:
set constraint constraint_name defferred
或者,您也可使用以下語法延遲全部約束:
set constraints all defferred
在這些命令中,您可使用關鍵字IMMEDIATE代替DEFERRED,將他們的強制模式改回當即模式。爲了看到實際的命令,咱們可使用一個簡單的示例:
SQL> drop table t; 表已丟棄。 SQL> create table t 2 (x int, 3 constraint x_greater_than_zero check(x>0) 4 deferrable initially immediate 5 ) 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命令,咱們就可以發現咱們已經違反了一些約束,而咱們的事務處理仍然有效。這個約束將會處理延遲模式,它不會當即檢查。
ACID是替代以下內容的首字母縮寫:
●原子性(Atomicity)——事務處理要麼所有進行,要麼不進行。
●一致性(Consistency)——事務處理要將數據庫從一種狀態變成另外一種狀態。
●隔離性(Isolation)——在事務處理提交以前,事務處理的效果不能由系統中其它事務處理看到。
●持久性(Durability)——一旦提交了事務處理,它就永久生效。
在Oracle中,事務處理具備原子性。換句話說,或者提交全部的工做,或者什麼工做都不提交。
這是很是重要的事務處理特性,任何事務處理都會將數據庫從一種邏輯上的一致狀態轉變爲另外一種邏輯上的一致狀態。這就是說,在事務處理開始以前,數據庫的全部數據都會知足您已經施加給數據庫的業務規則(約束)。
咱們所使用的表和觸發器以下所示:
SQL> drop table t; 表已丟棄。 SQL> create table t 2 ( x int, 3 constraint t_pk primary key(x) 4 ) 5 / 表已建立。 SQL> create trigger t_trigger 2 after update on T for each row 3 begin 4 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> begin 2 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> begin 2 update t set x=x+1; 3 end; 4 / Updated x=1 to x=2 Updated x=2 to x=3 PL/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 ---------- 2 SQL> 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 ---------- 2 SQL> 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 ---------- 2 SQL> select * from child; FK ---------- 2
這將會致使一致性數據知足咱們的約束。
在給定用戶隔離層次的狀況下,使用相同的輸入,採用相同的方式執行的相同的工做可能會致使不一樣的答案,這些隔離層次採用指定層次上許可(或者不符合規定)的三種「讀取」方式進行定義。它們是:
●髒讀取(Dirty read)——這種讀取方式的含義同它聽起來同樣糟糕。您能夠讀取沒有提交的「髒」數據。
●非可重複讀取(Non-repeatable read)——這種方式意味着,若是用戶在T1時刻讀取了一行,在T2時刻再次讀取一行,那麼這個行就可能發生改變。例如,它可能已經被刪除或者更新。
●影像讀取(Phantom read)——這種方式意味着若是用戶在T1時刻執行了一個查詢,在T2時刻再次執行它,就可能會有影響結果的附加行加入到數據庫中。在這種狀況下,這種讀取方式與非可重複讀取有所不一樣,您已經讀取的數據不會發生變化,而是會有比之前更多的知足查詢條件的數據。
SQL92採用了這三種「讀取」方式,而且基於它們的存在與否創建了4種隔離層次,見表13-2,它們是:
隔離層次 |
髒讀取 |
非重複讀取 |
影像讀取 |
非提交讀取(Read Uncommitted) |
容許 |
容許 |
容許 |
提交讀取(Read committed) |
禁止 |
容許 |
容許 |
可重複讀取(Repeatable Read) |
禁止 |
禁止 |
容許 |
串行讀取(Serializable) |
禁止 |
禁止 |
禁止 |
持久性是數據庫提供的最重要的特性之一。它能夠確保一旦事務處理提交以後,它的改變就會永久生效。它們不會因爲系統故障或者錯誤而「消失」。
開發多用戶、數據庫驅動的應用 的主要挑戰之一就是要最大化併發訪問(多個用戶同時訪問數據),並且與此同時,還要確保每一個用戶都能在一致的方式下讀取和修改數據。可以提供這些功能的鎖定和併發控制是全部數據庫的關鍵特性。
鎖定(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)會在一個會話擁有另外一個會話正在請求的資源上的鎖定時出現。正在進行請示的會話一直阻塞,直到佔用資源的會話翻譯鎖定資源爲止。例如,USER一、USER2同時查詢,1分鐘後,USER1修改了USER3的地址,USER2的會話仍然爲1分鐘前的數據,修改了USER3的電話號碼,此時,USER2的舊數據替換了USER1修改的數據。
悲觀鎖定(Pessimistic locking)聽起來很差,可是相信我,它實際並不是如此。在使用悲觀鎖定的時候,您就是在代表「我相信,某人頗有可能會改變我正在讀取(而且最終要更新)的相同數據,所以,在咱們花費時間,改變數據以前,我要鎖定數據庫中的行,防止其它會話更新它」。
爲了完成這項工做,咱們要使用相似以下語句的查詢:
select * from table where column1 = : old_column1 and column2 = : old.column2 and... and primary_key = : old_primary_key for update nowait;
因此咱們將會從這個語句中獲得三種結果:
●咱們將要獲取咱們的行,而且對這個行進行鎖定,防止被其它會話更新(可是阻止讀取)。
●咱們將會獲得ORA-00054 Resource Busy錯誤。其它的人已經鎖定了這個行,咱們必需要等待它。
●因爲某人已經對行進行了改變,因此咱們將會返回0行。
因爲咱們要在進行更新以前對行進行鎖定,因此這稱爲悲觀鎖定(pessimistic locking)。咱們對行維持不被改動並不樂觀(所以命名爲悲觀)。用戶應用中的活動流程應該以下所示:
●不進行鎖定查詢數據
SQL> select empno,ename,sal from emp where deptno=10; EMPNO ENAME SAL ---------- ---------- ---------- 7782 CLARK 2450 7839 KING 5000 7934 MILLER 1300
●容許終端用戶「查詢」數據。
SQL> select empno,ename,sal 2 from emp 3 where empno=7934 4 and ename='MILLER' 5 and sal=1300 6 for update nowait 7 / EMPNO ENAME SAL ---------- ---------- ---------- 7934 MILLER 1300
●假如咱們鎖定了數據行,咱們的應用就可使用更新,提交改變
SQL> update emp 2 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 dept 2 set loc='BOSTON' 3 where deptno=10; 已更新 1 行。
USER2更新
SQL> update dept 2 set loc='ALBANY' 3 where deptno=10; 已更新 1 行。
DEPTNO=10的LOC=’BOSTON’的記錄再也不存在,因此這裏更新了0行。咱們樂觀地認爲更新能夠完成。
悲觀鎖定最大的優點在於終端用戶能夠在他們花費時間進行修改以前,就可以發現他們的改變不可以進行。而在樂觀鎖定的狀況中,終端用戶將會花費大量的時間進行修改。
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> declare 2 cursor c1 is select username from t; 3 l_username varchar2(30); 4 begin 5 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 accounts 2 ( 3 account_id number, 4 account_type varchar2(20), 5 balance number 6 ); 表已建立。
精確快速地報告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表空間或者回滾段中的數據讀取出來,來爲客戶展現它數據的一致性。
在本章中,咱們首先了事務處理的構成。瞭解了事務處理控制語句,以及怎樣和在何時使用它們。
當討論併發控制的時候,咱們討論了2個重要而又複雜的主題,它們是鎖定和多版本讀取一致性。瞭解了死鎖、跟蹤文件分析死鎖、數據庫中避免阻塞等待的不一樣模式、悲觀鎖定和樂觀鎖定。
文章根據本身理解濃縮,僅供參考。
摘自:《Oracle編程入門經典》 清華大學出版社 http://www.tup.com.cn