資料庫事務與隔離級別

不洗碗工作室發表於2019-03-04

作者:不洗碗工作室 – Hotown

文章出處: 資料庫事務與隔離級別

版權歸作者所有,轉載請註明出處

在資料庫的使用中,我們常常把一系列操作的集合看作是一個獨立的單元,這種構成單一邏輯工作單元的集合被稱為事務

事務模型

一個資料庫系統需要維護事務的以下四種特性,它們被合稱為ACID,分別對應原子性(Atomicity),一致性(Consistency),隔離性(Isolation),永續性(Durability)。

一個簡單的事務模型

我們用T來指定這個模型,它的SQL大致是以下形式:

-- 操作1:扣除A賬戶10元
UPDATE account SET amount = amount - 10 WHERE user_name = `A`

-- 操作2:增加B賬戶10元
UPDATE account SET amount = amount + 10 WHERE user_name = `B`
複製程式碼

T是一個最簡單的轉賬模型,A使用者將10元轉到了B使用者的賬本上。

之後我們就用這個事務模型來解釋一下事務的四種特性。

一、原子性

原子性表示,一個事務所包含的操作集合是單一的,不可拆分的。而且事務中任何一個操作失敗,都要保證資料庫回滾到整個事務執行前的狀態。

在T事務中,有兩個操作,一個是在A賬戶扣除10元,一個是在B賬戶增加10元。這個兩個操作密不可分。

如果我們將這兩個操作看成兩個獨立的事務,那麼假設初始狀態:

A賬戶:100元
B賬戶:100元
複製程式碼

我們現在執行了操作A,A賬戶餘額變為90元。然後,然後!伺服器室由於某些不可控力,發生了爆炸。那麼最終結果將會變成:

A賬戶:90元
B賬戶:100元
複製程式碼

A和B就此開始了無止境的撕逼。。

B:你快給我轉錢啊!

A:我轉了啊!你看我的賬戶已經扣了10元了!

B:我這裡沒有收到啊!你自己看,還是100元!

……

二、一致性

一致性原則要求事務的執行不改變資料庫的一致。即事務執行前如果資料庫一致,事務執行後,這種一致性仍然存在。

以T事務為例,T執行前,A和B的賬戶餘額總和為200元,那麼我們要保證在T執行後,A和B的賬戶餘額總合仍然為200元。

三、永續性

永續性的原則要求一旦事務成功完成執行,並且提交到資料庫,那麼這一次更新將會持久的。也就是說,只要事務成功執行,任何的系統故障都不能撤銷這一次事務的提交。

這個概念可能出現一些小漏洞,比如如果事務的結果儲存在記憶體中,那麼一旦當機,所有的資料都會消失,我們需要將資料提交到外部磁碟上,並且做好更新資訊的記錄,使資料庫在當機後重啟悽然能恢復到之前的狀態。由於這個概念不屬於我們的討論範圍,這裡也就不再贅述。

四、隔離性

隔離性確保事務併發執行後的系統狀態與這些事務以某種次序序列執行以後的狀態是等價的。

如果有多個事務併發執行,即使我們確保事務的原子性和一致性,這些操作在執行時也並不是嚴格的序列,而是以某種不可見的形式交叉執行,這種不可見行很可能會導致最終狀態的不一致。

舉個例子,我們將之前的事務T記作事務T1,並將T1中的操作細分,他們在系統中的實際操作應該大致是這樣的:

/**
* read(x):從資料庫中將x傳送到執行read操作的事務的主存緩衝區中
*
* write(x):從執行write的事務的主存緩衝區中將x取出並寫回資料庫(其實還有一個commit過程,這裡先忽略)
*/

read(A);
A := A-10;
write(A);


read(B);
B := B+10;
write(B);

複製程式碼

除此以外,我們再定義一個T2,計算A+B的值:

read(A);
read(B);
A := A+B;
複製程式碼

並行的事務會如何執行呢?如果運氣好,它可能會按照T1,T2的順序完整執行,那麼最終我們得到的temp的狀態應該是200。

但是如果出現一種情況,當T1中的A扣款成功,並切入資料庫,而在執行給B增加餘額的操作時,並沒有全部完成,而是執行完B := B+10以後,開始執行T2,雖然B變數確實發生了改變,但是它還沒有被寫進資料庫中,所以T2中計算出的temp變成了90+100=190。

大致流程會是這個樣子:

-- T1
read(A):
A := A-10;
write(A); --這裡A在資料庫中的值變成了90

read(B);
B := B+10; --這裡B確實發生了改變,但是並未提交至資料庫

-- T2
read(A); --A = 90
read(B); --B = 100(不是110)
temp := A+B; --得到190 

--T1
write(B) --這裡B的修改被提交到資料庫

複製程式碼

為了確保隔離性,資料庫系統中存在一種併發控制系統,來完成這一職責。

事務的隔離級別

在介紹事務的隔離級別前,先來介紹一下髒讀幻讀不可重複讀的概念。

髒讀、幻讀、不可重複讀

  • 髒讀:髒讀是指,當一個事務在訪問某一資料,並且修改了這一資料,但是在commit之前,另外一個事務也訪問了同一資料,然後做了修改。大致模型如下(這裡用到了之前忽略的commit操作,這個操作是指事務完成後,進入提交狀態):
-- T1
read(A);
A := A+1;
write(A);

--T2
read(A);
A := A+2;
write(A);
commit;

-- T1
commit;
複製程式碼
  • 不可重複讀:指在一個事務中,對同一資料進行了兩次讀取,但是在這個事務還未結束的時候(第一次讀取之後,第二次讀取之前),另一事務讀取了同一資料,並進行了修改,那就導致了兩次重複讀取的資料不一致,形成了不可重複讀狀態。
-- 假設初始狀態,A=10

-- T1
read(A); -- A = 10;

-- T2
read(A);
A = A+10;
write(A);
commit; -- A = 20;

-- T1
read(A); -- A = 20,與第一次不同
複製程式碼
  • 幻讀:幻讀發生在兩個事務非獨立執行的情況。下面用SQL演示這種情況:

-- T1
UPDATE users SET status = 1;

-- T2
insert users (`status`) values (`0`)
複製程式碼

然後執行T1操作的使用者驚奇的發現,明明把所有的user狀態都置1了啊,怎麼還有一個0 ??????

事務隔離級別

  • 可序列化(Serializable):sql中最高的隔離性級別,能夠避免髒讀,幻讀,不可重複讀。代價也相對沉重,會大大影響資料庫的效能。

  • 可重複讀(Repeatable read):只允許讀取已提交的資料,而且在一個事務兩次讀取一個資料項期間,其他事務不得更新該資料。這種狀態不能避免幻讀

  • 已提交讀(Read committed):只允許讀取已提交資料,但不要求可重複讀。這種狀態只能避免髒讀

  • 未提交讀(Read uncommitted):允許讀取未提交的資料。這是最低的隔離級別,髒讀,幻讀,不可重複讀都無法避免。

Attention:所有的隔離級別都不允許髒寫,即如果一個資料項已經被另外一個尚未提交或終止的事務寫入,則不允許其他事務對它進行寫入。

相關文章