天天看點

分布式事務之淺談資料庫事務

事務是什麼

是以一種可靠,一緻的方式,通路和操作資料庫中的程式單元。

事務的ACID原則

  • 原子性 :事務作為一個整體被執行,包含在其中的對資料庫的操作要麼全部被執行,要麼都不執行。
  • 一緻性:事務應確定資料庫的狀态從一個一緻狀态轉變為另一個一緻狀态。一緻狀态的含義是資料庫中的資料應滿足完整性限制。
  • 隔離性:多個事務并發執行時,一個事務的執行不應影響其他事務的執行。
  • 持久性:一個事務一旦送出,他對資料庫的修改應該永久儲存在資料庫中。

舉例一

用一個常用的“A賬戶向B賬号彙錢”的例子來說明如何通過資料庫事務保證資料的準确性和完整性。

  • A賬戶與B賬戶初始餘額各為500元。
  • 對A賬戶做減法操作(500-100)。
  • 對B賬戶做加法操作(500+100)。

原子性:

保證所有過程要麼都執行,要麼都不執行。一旦在執行某一步驟的過程中發生問題,就需要執行復原操作。 假如執行到B賬戶做加法操作時,B賬戶突然不可用(比如被登出),那麼之前的所有操作都應該復原到執行事務之前的狀态。

一緻性:

在轉賬之前,A和B的賬戶中共有500+500=1000元錢。在轉賬之後,A和B的賬戶中共有400+600=1000元。也就是說,資料的狀态在執行該事務操作之後從一個狀态改變到了另外一個狀态。

隔離性:

在A向B轉賬的整個過程中,隻要事務還沒有送出(commit),查詢A賬戶和B賬戶的時候,兩個賬戶裡面的錢的數量都不會有變化。

如果在A給B轉賬的同時,有另外一個事務執行了C給B轉賬的操作,那麼當兩個事務都結束的時候,B賬戶裡面的錢應該是A轉給B的錢加上C轉給B的錢再加上自己原有的錢。

持久性:

一旦轉賬成功(事務送出),兩個賬戶的裡面的錢就會真的發生變化(會把資料寫入資料庫做持久化儲存)。

以上介紹完事務的四大特性(簡稱ACID),現在重點來說明下事務的隔離性,當多個線程都開啟事務操作資料庫中的資料時,資料庫系統要能進行隔離操作,以保證各個線程擷取資料的準确性,在介紹資料庫提供的各種隔離級别之前,我們先看看如果不考慮事務的隔離性,會發生的幾種問題:

  • 髒讀: 髒讀是指在一個事務處理過程裡讀取了另一個未送出的事務中的資料。

    當一個事務正在多次修改某個資料,而在這個事務中這多次的修改都還未送出,這時一個并發的事務來通路該資料,就會造成兩個事務得到的資料不一緻。例如:使用者A向使用者B轉賬100元,對應SQL指令如下

update account set money=money + 100 where name=’B’; 
update account set money=money - 100 where name=’A’;
           

當隻執行第一條SQL時,A通知B檢視賬戶,B發現确實錢已到賬(此時即發生了髒讀),而之後無論第二條SQL是否執行,隻要該事務不送出,則所有操作都将復原,那麼當B以後再次檢視賬戶時就會發現錢其實并沒有轉。

  • 不可重複讀: 不可重複讀是指在對于資料庫中的某個資料,一個事務範圍内多次查詢卻傳回了不同的資料值,這是由于在查詢間隔,被另一個事務修改并送出了。

    例如事務T1在讀取某一資料,而事務T2立馬修改了這個資料并且送出事務給資料庫,事務T1再次讀取該資料就得到了不同的結果,發生了不可重複讀。

    不可重複讀和髒讀的差別是,髒讀是某一事務讀取了另一個事務未送出的髒資料,而不可重複讀則是讀取了前一事務送出的資料。

  • 幻讀: 幻讀是事務非獨立執行時發生的一種現象。例如事務T1對一個表中所有的行的某個資料項做了從1修改為2的操作,這時事務T2又對這個表中插入了一行資料項,而這個資料項的數值還是為1并且送出給資料庫。而操作事務T1的使用者如果再檢視剛剛修改的資料,會發現還有一行沒有修改,其實這行是從事務T2中添加的,就好像産生幻覺一樣,這就是發生了幻讀。

幻讀和不可重複讀都是讀取了另一條已經送出的事務(這點就髒讀不同),所不同的是不可重複讀查詢的都是同一個資料項,而幻讀針對的是一批資料整體(比如資料的個數)。

事務的隔離級别

現在來看看MySQL資料庫為我們提供的四種隔離級别:

  • Serializable (串行化): 可避免髒讀、不可重複讀、幻讀的發生。
  • Repeatable read (可重複讀): 可避免髒讀、不可重複讀的發生。
  • Read committed (讀已送出): 可避免髒讀的發生。
  • Read uncommitted (讀未送出): 最低級别,任何情況都無法保證。

以上四種隔離級别最高的是Serializable級别,最低的是Read uncommitted級别,當然級别越高,執行效率就越低。像Serializable這樣的級别,就是以鎖表的方式(類似于Java多線程中的鎖)使得其他的線程隻能在鎖外等待,是以平時選用何種隔離級别應該根據實際情況。在MySQL資料庫中預設的隔離級别為Repeatable read (可重複讀)。

在MySQL資料庫中,支援上面四種隔離級别,預設的為Repeatable read (可重複讀);而在Oracle資料庫中,隻支援Serializable (串行化)級别和Read committed (讀已送出)這兩種級别,其中預設的為Read committed級别。

在MySQL資料庫中檢視目前事務的隔離級别:

在MySQL資料庫中設定事務的隔離級别:

set  [glogal | session]  transaction isolation level 隔離級别名稱;
set tx_isolation=’隔離級别名稱’;
           

舉例二

重點示範一下可重複讀

### 事務t1
START TRANSACTION;
UPDATE account SET amount = amount - 100 WHERE username = 'A';
UPDATE account SET amount = amount + 100 WHERE username = 'B';
COMMIT;
           
### 事務t2
START TRANSACTION;
SELECT * FROM account;
SELECT * FROM account where username = 'A';
COMMIT;
           

假設A賬戶與B賬戶最初各有100元

此時開啟了事務t2,執行第一條查詢語句,如下

### 事務t2
START TRANSACTION;
SELECT * FROM account;
           

此時查詢的A與B賬戶餘額都是100元,還未執行到第二條查詢語句的時候,若此時開啟了事務t1,且事務t1已執行完成,已送出。

事務t2繼續執行下一個查詢語句,此時查詢出A的餘額應該是100,而不是0。事務t2送出之後,再查詢A的餘額才是0。

可重複讀就是在一個事務之内,重複多次讀取一條資料,前後查詢的結果應該是一緻的。

讀未送出

事務t1執行到對A賬戶做減法操作,注意此時還未送出,事務t2查詢出來的資料是事務t1未送出的資料,也就是發生了髒讀。

讀已送出

事務t2查詢的資料,前後可能會不一緻,例如事務t2第二次查詢執行之前,事務t1已送出,此時查詢A的餘額就是0。意思說其他事務隻要送出了,就可以讀到。

串行化

就是事務的排隊執行,等待前一個事務執行完成。