Oracle資料庫之事務
Oracle資料庫之事務
1. 什麼是事務
在資料庫中事務是工作的邏輯單元,一個事務是由一個或多個完成一組的相關行為的SQL語句組成,通過事務機制確保這一組SQL語句所作的操作要麼都成功執行,完成整個工作單元操作,要麼一個也不執行。
如:網上轉帳就是典型的要用事務來處理,用以保證資料的一致性。
2. 事務特性
SQL92標準定義了資料庫事務的四個特點:
- 原子性(Atomicity):一個事務裡面所有包含的SQL語句是一個執行整體,不可分割,要麼都做,要麼都不做。
- 一致性(Consistency):事務開始時,資料庫中的資料是一致的,事務結束時,資料庫的資料也應該是一致的。
- 隔離性(Isolation):是指資料庫允許多個併發事務同時對其中的資料進行讀寫和修改的能力,隔離性可以防止事務的併發執行時,由於他們的操作命令交叉執行而導致的資料不一致狀態。
- 永續性 (Durability) : 是指當事務結束後,它對資料庫中的影響是永久的,即便系統遇到故障的情況下,資料也不會丟失。
一組SQL語句操作要成為事務,資料庫管理系統必須保證這組操作的原子性(Atomicity)、一致性(consistency)、隔離性(Isolation)和永續性(Durability),這就是ACID特性。
3. 資料異常
因為Oracle中支援多個事務併發執行,所以會出現下面的資料異常。
3.1 髒讀
當一個事務修改資料時,另一事務讀取了該資料,但是第一個事務由於某種原因取消對資料修改,使資料返回了原狀態,這是第二個事務讀取的資料與資料庫中資料不一致,這就叫髒讀。
如:事務T1修改了一條資料,但是還未提交,事務T2恰好讀取到了這條修改後了的資料,此時T1將事務回滾,這個時候T2讀取到的資料就是髒資料。
3.2 不可重複讀
是指一個事務讀取資料庫中的資料後,另一個事務則更新了資料,當第一個事務再次讀取其中的資料時,就會發現資料已經發生了改變,這就是不可重複讀取。不可重複讀取所導致的結果就是一個事務前後兩次讀取的資料不相同。
如:事務T1讀取一行記錄,緊接著事務T2修改了T1剛剛讀取的記錄,然後T1再次查詢,發現與第一次讀取的記錄不同。
3.3 幻讀
如果一個事務基於某個條件讀取資料後,另一個事務則更新了同一個表中的資料,這時第一個事務再次讀取資料時,根據搜尋的條件返回了不同的行,這就是幻讀。
如:事務T1讀取一條指定where條件的語句,返回結果集。此時事務T2插入一行新記錄,恰好滿足T1的where條件。然後T1使用相同的條件再次查詢,結果集中可以看到T2插入的記錄,這條新紀錄就是幻讀。
事務中遇到的這些異常與事務的隔離性設定有關,事務的隔離性設定越多,異常就出現的越少,但併發效果就越低,事務的隔離性設定越少,異常出現的越多,併發效果越高。
4. 事務隔離級別
針對讀取資料時可能產生的不一致現象,在SQL92標準中定義了4個事務的隔離級別:
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
Read uncommitted(讀未提交) | 是 | 是 | 是 |
Read committed(讀已提交) | 否 | 是 | 是 |
Repeatable read(可重複讀) | 否 | 否 | 是 |
Serializable(序列讀) | 否 | 否 | 否 |
Oracle預設的隔離級別是read committed。
Oracle支援上述四種隔離級別中的兩種:read committed 和serializable。除此之外,Oralce中還定義Read only和Read write隔離級別。
Read only:事務中不能有任何修改資料庫中資料的操作語句,是Serializable的一個子集。
Read write:它是預設設定,該選項表示在事務中可以有訪問語句、修改語句,但不經常使用。
設定隔離級別
設定一個事務的隔離級別:
- SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
- SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
- SET TRANSACTION READ ONLY;
- SET TRANSACTION READ WRITE;
注意:這些語句是互斥的,不能同時設定兩個或兩個以上的選項。
設定單個會話的隔離級別:
- ALTER SESSION SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
- ALTER SESSION SET TRANSACTION ISOLATION SERIALIZABLE;
5. 事務控制命令
5.1 提交事務
在執行使用COMMIT
語句可以提交事務,當執行了COMMIT語句後,會確認事務的變化,結束事務,刪除儲存點,釋放鎖。當使用COMMIT語句結束事務之後,其他會話將可以檢視到事務變化後的新資料。
5.2 回滾事務
儲存點(savepoint):是事務中的一點,用於取消部分事務,當結束事務時,會自動的刪除該事務所定義的所有儲存點。當執行ROLLBACK時,通過指定儲存點可以回退到指定的點。
設定儲存點:
sql> Savepoint a;
刪除儲存點:
sql> Release Savepoint a;
回滾部分事務:
sql> Rollback To a;
回滾全部事務:
sql> Rollback;
6. 資料庫鎖
資料庫是一個多使用者使用的共享資源。當多個使用者併發地存取資料時,在資料庫中就會產生多個事務同時存取同一資料的情況。若對併發操作不加控制就可能會讀取和儲存不正確的資料,破壞資料庫的一致性。
在資料庫中有兩種基本的鎖型別:排它鎖(Exclusive Locks,即X鎖)和共享鎖(Share Locks,即S鎖)。當資料物件被加上排它鎖時,其他的事務不能對它讀取和修改;加了共享鎖的資料物件可以被其他事務讀取,但不能修改。
6.1 鎖分類
根據保護物件的不同,Oracle資料庫鎖可分為:
- DML lock(data locks,資料鎖):用於保護資料的完整性。
- DDL lock(dictionary locks,字典鎖):用於保護資料庫物件的結構(例如表、檢視、索引的結構定義)。
- Internal locks 和latches(內部鎖與閂):保護內部資料庫結構。
- Distributed locks(分散式鎖):用於OPS(並行伺服器)中。
- PCM locks(並行快取記憶體管理鎖):用於OPS(並行伺服器)中。
在Oracle中最主要的鎖是DML鎖,DML鎖的目的在於保證併發情況下的資料完整性。在Oracle資料庫中,DML鎖主要包括TM鎖和TX鎖,其中TM鎖稱為表級鎖,TX鎖稱為事務鎖或行級鎖。
鎖出現在資料共享的場合,用來保證資料的一致性。當多個會話同時修改一個表時,需要對資料進行相應的鎖定。
鎖有“共享鎖”、“排它鎖”,“共享排它鎖”等多種型別,而且每種型別又有“行級鎖” (一次鎖住一條記錄),“頁級鎖” (一次鎖住一頁,即資料庫中儲存記錄的最小可分配單元),“表級鎖” (鎖住整個表)。
6.2 共享鎖(S鎖)
可通過lock table in share mode命令新增該S鎖。在該鎖定模式下,不允許任何使用者更新表。但是允許其他使用者發出select …from for update命令對錶新增RS鎖。
6.3 排他鎖(X鎖)
可通過lock table in exclusive mode命令新增X鎖。在該鎖定模式下,其他使用者不能對錶進行任何的DML和DDL操作,該表上只能進行查詢。
6.4 行級共享鎖(RS鎖)
通常是通過select … from for update語句新增的,同時該方法也是我們用來手工鎖定某些記錄的主要方法。比如,當我們在查詢某些記錄的過程中,不希望其他使用者對查詢的記錄進行更新操作,則可以發出這樣的語句。當資料使用完畢以後,直接發出rollback命令將鎖定解除。當表上添加了RS鎖定以後,不允許其他事務對相同的表新增排他鎖,但是允許其他的事務通過DML語句或lock命令鎖定相同表裡的其他資料行。
6.5 行級排他鎖(RX鎖)
當進行DML操作時會自動在被更新的表上新增RX鎖,或者也可以通過執行lock命令顯式的在表上新增RX鎖。在該鎖定模式下,允許其他的事務通過DML語句修改相同表裡的其他資料行,或通過lock命令對相同表新增RX鎖定,但是不允許其他事務對相同的表新增排他鎖(X鎖)。
6.6 共享行級排他鎖(SRX鎖)
通過lock table in share row exclusive mode命令新增SRX鎖。該鎖定模式比行級排他鎖和共享鎖的級別都要高,這時不能對相同的表進行DML操作,也不能新增共享鎖。
上述幾種鎖模式中,RS鎖是限制最少的鎖,X鎖是限制最多的鎖。它們的相容關係如下:
基本上所有的鎖都可以由Oracle內部自動建立和釋放,但是其中的DDL和DML鎖是可以通過命令進行管理的,命令語法:
LOCK table_name IN [row share][row exclusive][share][share row exclusive][exclusive] MODE [NOWAIT];
下圖列出產生鎖定模式的SQL語句:
當程式對所做的修改進行提交(Commit)或回滾(Rollback)後,鎖住的資源便會得到釋放,從而允許其他使用者進行操作。如果兩個事務,分別鎖定一部分資料,而都在等待對方釋放鎖才能完成事務操作,這種情況下就會發生死鎖
7. 資料庫事務實現機制
幾乎所有的資料庫管理系統中,事務管理的機制都是通過使用日誌檔案來實現的,我們來簡單介紹一下日誌的工作方式。
當用戶執行一條修改資料庫的DML語句時,DBMS自動在日誌檔案中寫一條記錄,顯示被這條語句影響的每一條記錄的兩個副本。一個副本顯示變化前的記錄,另一個副本顯示變化後的記錄。當日志寫完之後,DBMS才實際對磁碟中的記錄進行修改。
如果使用者隨後執行COMMIT語句,事務結束也被記錄在事務日誌中。如果使用者執行ROLLBACK語句,DBMS檢查日誌,找出自事務開始以來被修改的記錄“以前”的樣子,然後使用這些資訊恢復它們以前的狀態,有效地撤銷事務期間對資料庫所做的修改。
如果系統出錯,系統操作員通常通過執行DBMS提供的特殊恢復程式來複原資料庫。恢復程式檢查到事務日誌末尾,查詢故障之前沒有被提交的事務。恢復程式回滾沒有完全完成的事務,以便僅有被提交的事務反映到資料庫中,而故障中正處理的事務被回滾。
事務日誌的使用明顯增加了更新資料庫的開銷。在實際中,主流商用DBMS產品使用的日誌技術比上述描述的方案更復雜,用以減小這種開銷。此外,事務日誌通常被儲存在高速磁碟驅動器中,不同於儲存資料庫的磁碟,以減小磁碟訪問競爭。某些個人計算機DBMS產品允許關閉事務日誌效能,以提高DBMS的效能。
8. 示例
銀行轉帳的例子是最經典的事務示例:
使用者把錢從一個銀行賬號轉賬至另一個銀行賬號,需要將資金從一個銀行賬號中取出,然後再存入另一個銀行賬號中。理想來說,這兩次操作都應該成功。但是,如果有錯誤發生,則兩次操作都應該失敗,否則的話,操作之後其中一個賬號中的金額將會是錯誤的,整個操作過程應該是原子性的,兩個操作都是一個原子事務操作的一部分。
示例:
-- 從賬戶一向賬戶二轉賬 DECLARE v_money NUMBER(8, 2); -- 轉賬金額 v_balance account.balance%TYPE; -- 賬戶餘額 BEGIN v_money := &轉賬金額; -- 輸入轉賬金額 -- 從賬戶一減錢 UPDATE account SET balance = balance - v_money WHERE id=&轉出賬戶 RETURNING balance INTO v_balance; IF SQL%NOTFOUND THEN RAISE_APPLICATION_ERROR(-20001, '沒有該賬戶:'||&轉出賬戶); END IF; IF v_balance < 0 THEN RAISE_APPLICATION_ERROR(-20002, '賬戶餘額不足'); END IF; -- 向賬戶二加錢 UPDATE account SET balance = balance + v_money WHERE id=&轉入賬戶; IF SQL%NOTFOUND THEN RAISE_APPLICATION_ERROR(-20001, '沒有該賬戶:'||&轉入賬戶); END IF; -- 如果沒有異常,則提交事務 COMMIT; DBMS_OUTPUT.PUT_LINE('轉賬成功'); EXCEPTION WHEN OTHERS THEN ROLLBACK; -- 出現異常則回滾事務 DBMS_OUTPUT.PUT_LINE('轉賬失敗:'); DBMS_OUTPUT.PUT_LINE(SQLERRM); END;