Oracle的資料併發與一致性詳解(上)

davidtim發表於2021-09-09

今天想了解下oracle中事務與鎖的原理,但百度了半天,發現網上介紹的內容要麼太短,要麼版本太舊,而且抄襲現象嚴重,所以乾脆查官方幫助文件(oracle 11.2),並將其精華整理成中文,供大家一起學習。

       本篇將從資料併發與一致性概念開始,依次介紹事務隔離級別、鎖機制、自動鎖、手動鎖、使用者自定義鎖的相關內容。

一、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用的是預設隔離級別READ COMMITTED


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是預設的 READ COMMITTED


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錯誤,只讀事務在產生一個報告時很有效。

 

——限於精力,今天先寫到這裡,明天繼續更新下篇——鎖機制、手動鎖、使用者自定義鎖。

       參考資料:《》

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3349/viewspace-2810085/,如需轉載,請註明出處,否則將追究法律責任。

相關文章