今天想了解下oracle中事務與鎖的原理,但百度了半天,發現網上介紹的內容要么太短,要么版本太舊,而且抄襲現象嚴重,所以干脆查官方幫助文檔(oracle 11.2),並將其精華整理成中文,供大家一起學習。
本篇將從數據並發與一致性概念開始,依次介紹事務隔離級別、鎖機制、自動鎖、手動鎖、用戶自定義鎖的相關內容。
請尊重作者勞動成果,轉載請標明原文鏈接:
https://www.cnblogs.com/jpcflyer/p/9164100.html
一、Oracle數據並發與一致性概念
在以前單用戶的數據庫環境中,我們根本就不需要關心數據一致性的問題,因為根本就不會有多個用戶在同一時間修改同一數據。但在現在的多用戶數據庫環境中,必須允許同時發生多個事務,而且這些事務可能會訪問同一數據,此外,還要保證這些事務的一致性。因此多用戶數據庫必須提供以下兩個基本功能:
- 數據並發:即允許多用戶同時訪問同一數據
- 數據一致性:即每個用戶看到的數據都是一致的
為了描述事務並發運行時的一致性行為,研究人員定義了一種事務隔離模型,稱之為serializability(序列化)。這種可串行化事務操作使得它看起來似乎沒有其它用戶在操作數據。雖然這種序列化機制在一般情況下是可用的,但在並發要求高的場景,它會嚴重影響系統的吞吐能力。一般情況下,需要在事務隔離級別與性能間作一個取舍。
Oracle通過使用multiversion consistency model(多版本一致性模型)、以及各種鎖和事務來維護數據一致性,下面介紹下相關概念。
1.Multiversion Read Consistency(多版本讀一致性)
多版本指的是同時存在數據的多個版本,意味着oracle有以下兩個特性:
- read-consistent queries(讀一致性查詢)
查詢返回的數據是已提提交的,在某一時間點是一致的。(注意:oracle中是不會有臟讀的,為什么呢?原因是第一條會話插入一條記錄不提交,第二個會話再查詢時發現這個事務沒有commit,從而會找到這個事務的事務槽,事務槽中記錄着該行未修改前的值存放在undo的位置,然后把該undo塊加載到內存構造出CR塊,查詢會讀取CR塊中的值返回給客戶。)
- nonblocking queries(非阻塞查詢)
數據的讀和寫不會相互阻塞。
2.Statement-Level Read Consistency(聲明級讀一致性)
Oracle總是強制保證聲明級的讀一致性,確保查詢返回的數據在同一時間點是已提交的(原因已在上面提及)。
3.Transaction-Leval Read Consistency(事務級讀一致性)
Oracle可以提供事務中所有查詢的一致性,即事務中每個聲明看到的數據都是某一點的數據,這個點指的是事務開始的點。在序列化事務中的查詢只能看到自己本事務發生的修改。事務級讀一致性產生了可重復讀,且不會產生幻影讀。
4.Read Consistency and Transaction Table(讀一致性和事務表)
Oracle使用了事務表來確定當數據庫開始修改一個塊時,是否有未提交的事務,這個事務表也稱為interested transaction list(ITL)。事務表中描述了哪個事務有行鎖、哪一行包含已提交或未提交的修改。
5.Locking Mechanisms(鎖機制)
一般來說,多用戶數據庫會使用多種數據鎖的形式來解決數據的並發與一致性問題,本文后面會有鎖的詳細介紹。
6.ANSI/ISO Transaction Isotation Levals(ANSI/ISO事務隔離級別)
ANSI和ISO都采納的SQL標准中,定義了四個級別的事務隔離。這些不同級別對事務吞吐量有不同影響。這些隔離級別定義是為了預防兩個並發事務會產生的一些現象,這些現象包括:
- 臟讀:一個事務讀取了另一個事務沒有提交的數據
- 不可重復讀:一個事務重復讀取剛才已讀過的數據,結果兩次數據不一致,在此期間,其它事務對此數據已修改並提交
- 幻影讀:一個事務重復讀取滿足查詢條件的記錄數,結果兩次數據不一致,在此期間,其它事務插入了符合此查詢條件的數據
SQL標准中根據隔離級別允許發生的現象,定義了四種隔離級別:
Oracle數據庫提供了read committed(默認級別)和serializable兩種隔離級別,同時還支持只讀模式。
二、事務隔離級別
上面已經提到ANSI的四種事務隔離級別,下面來詳細介紹oracle數據庫提供的三種事務隔離級別:read committed, serializable,read-only。
1.Read Committed事務隔離級別
在此級別中,事務中查詢到的數據都是在此查詢前已經提交的。這種隔離級別避免了讀取臟數據。然而數據庫並不阻止其它事務修改一個所讀取的數據,其它事務可能會在查詢執行期間修改。因此 ,一個事務運行同樣的查詢兩次,可能會遇到不可重復讀和幻影讀。
- read committed隔離級別中的讀一致性
每個查詢都會提供一個一致性的結果集,其中不需要用戶做什么(這里的查詢也包含像update中where這樣的隱式查詢)。
- read committed隔離級別中的寫沖突
在一個read committed事務中,當事務要更改一行,而這行已經被另外一個未提交事務修改了(有時稱之為blocking transaction),這里會發生寫沖突。此時這個事務會等待blocking transaction結束,並有以下兩個選項:
- 如果blocking transaction回滾,那么waiting transaction會修改之前被locked的行
- 如果blocking transaction提交然后釋放鎖,那么waiting transaction會在改變后的數據基礎上,進行更新
下表顯示了事務1(可以是read committed或serializable)與事務2(read committed)的典型交互,稱之為lost update(丟失更新)。
事務1 | 事務2 | 說明 |
---|---|---|
SQL> SELECT last_name, salary
FROM employees WHERE last_name
IN ('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 6200
Greene 9500
|
|
|
SQL> UPDATE employees SET salary
= 7000 WHERE last_name = 'Banda';
|
事務1用的是默認隔離級別 |
|
SQL> SET TRANSACTION ISOLATION
LEVEL READ COMMITTED;
|
|
|
SQL> SELECT last_name, salary
FROM employees WHERE last_name IN
('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 6200
Greene 9500
|
事務2通過使用oracle的讀一致性得到了事務1更新前的數據 |
|
SQL> UPDATE employees SET salary =
9900 WHERE last_name = 'Greene';
|
|
|
SQL> INSERT INTO employees
(employee_id, last_name, email,
hire_date, job_id) VALUES (210,
'Hintz', 'JHINTZ', SYSDATE,
'SH_CLERK');
|
事務1插入了employee Hintz,但並沒有提交 |
|
SQL> SELECT last_name, salary
FROM employees WHERE last_name IN
('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 6200
Greene 9900
|
事務2看不到事務1未提交的Hintz信息 |
|
SQL> UPDATE employees SET salary =
6300 WHERE last_name = 'Banda';
-- prompt does not return
|
事務2嘗試去更新被事務鎖住的Banda信息,產生了寫沖突,此時事務2要等到事務1結束后再執行 |
|
SQL> COMMIT;
|
|
|
1 row updated.
SQL>
|
事務1提交,結束了事務,事務2繼續處理 |
|
SQL> SELECT last_name, salary
FROM employees WHERE last_name IN
('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 6300
Greene 9900
Hintz
|
|
|
COMMIT;
|
|
|
SQL> SELECT last_name, salary
FROM employees WHERE last_name
IN ('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 6300
Greene 9900
Hintz
|
關於丟失更新的問題,后面有時間再討論。
2.Serializable事務隔離級別
在序列化隔離級別中,事務可以看到的是事務開始時已經提交的或事務自己做的修改。此隔離級別適合下面的場景:
- 超大數據庫並且事務很小,每個事務只更新幾行
- 在兩個並發事務修改相同行的幾率相對比較低的場景
- 有較長的事務,但主要是只讀事務的時候
在序列化隔離級別中,讀一致性從通常的語句級擴展成整個事務級。事務中讀取的任何行,再次讀時保證是相同的。序列化事務不會遇到臟讀、不可重復讀、幻影讀的問題。
Oracle允許序列化事務修改數據,不過如果有其它事務修改,那么這個事物必須在序列化事務開始之前就提交。當一個序列化事務企業修改一行,而該行被別的事務修改,且在序列化事務開始之后才提交,這時候會報ORA-08177:Cannot serialize access for this transaction。此時,應用可以采取以下動作:
- 提交事務
- 執行其它不同的語句,也許會回滾到之前的savepoint
- 回滾整個事務
下面顯示一個序列化事務是如何與其它事務交互的。如果一個序列化任務不去嘗試修改其它事務在序列化事務開始后提交的數據,那么serialized access問題可以避免。
事務1 | 事務2 | 事務3 |
---|---|---|
SQL> SELECT last_name, salary
FROM employees WHERE last_name
IN ('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 6200
Greene 9500
|
|
|
SQL> UPDATE employees SET salary
= 7000 WHERE last_name = 'Banda';
|
事務1是默認的 |
|
SQL> SET TRANSACTION ISOLATION
LEVEL SERIALIZABLE;
|
|
|
SQL> SELECT last_name, salary
FROM employees WHERE last_name
IN ('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 6200
Greene 9500
|
|
|
SQL> UPDATE employees SET salary =
9900 WHERE last_name = 'Greene';
|
|
|
SQL> INSERT INTO employees
(employee_id, last_name, email,
hire_date, job_id) VALUES (210,
'Hintz', 'JHINTZ', SYSDATE,
'SH_CLERK');
|
|
|
SQL> COMMIT;
|
|
|
SQL> SELECT last_name, salary
FROM employees WHERE last_name
IN ('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 7000
Greene 9500
Hintz
|
SQL> SELECT last_name, salary
FROM employees WHERE last_name IN
('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 6200
Greene 9900
|
注意:oracle的讀一致性使得事務2的前后讀取是一致的,即事務1的插入和更新操作對事務2來說是不可見的 |
COMMIT;
|
|
|
SQL> SELECT last_name, salary
FROM employees WHERE last_name
IN ('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 7000
Greene 9900
Hintz
|
SQL> SELECT last_name, salary
FROM employees WHERE last_name
IN ('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 7000
Greene 9900
Hintz
|
|
SQL> UPDATE employees SET salary
= 7100 WHERE last_name = 'Hintz';
|
|
|
SQL> SET TRANSACTION ISOLATION
LEVEL SERIALIZABLE;
|
|
|
SQL> UPDATE employees SET salary =
7200 WHERE last_name = 'Hintz';
-- prompt does not return
|
|
|
SQL> COMMIT;
|
|
|
UPDATE employees SET salary = 7200
WHERE last_name = 'Hintz'
*
ERROR at line 1:
ORA-08177: can't serialize access
for this transaction
|
報錯原因在於事務3的提交是在事務4開始之后,沒有滿足序列化 |
|
SQL> ROLLBACK;
|
事務2回滾以結束事務 |
|
SQL> SET TRANSACTION ISOLATION
LEVEL SERIALIZABLE;
|
|
|
SQL> SELECT last_name, salary
FROM employees WHERE last_name
IN ('Banda','Greene','Hintz');
LAST_NAME SALARY
------------- ----------
Banda 7100
Greene 9500
Hintz 7100
|
|
|
SQL> UPDATE employees SET salary =
7200 WHERE last_name = 'Hintz';
1 row updated.
|
|
|
SQL> COMMIT;
|
|
3.Read-Only事務隔離級別
只讀隔離級別和序列化隔離級別很像,只是在只讀事務中,不允許有修改操作,除非是用sys用戶。因此只讀事務不會有ORA-08177錯誤,只讀事務在產生一個報告時很有效。
——限於精力,今天先寫到這里,明天繼續更新下篇——鎖機制、手動鎖、用戶自定義鎖。
參考資料:《Oracle官網在線幫助文檔》