天天看點

資料庫事務的四大特性和隔離級别,一文帶你看通透1. 什麼是事務2. 事務的四大特性(ACID)3. 事務的隔離級别

1. 什麼是事務

事務

事務是一個整體,由一條或者多條SQL語句組成,這些SQL語句要麼都執行成功,要麼都執行失敗,

隻要有一條SQL出現異常,整個操作就會復原,整個業務執行失敗

比如: 銀行的轉賬業務,張三給李四轉賬500元 , 至少要操作兩次資料庫, 張三 -500, 李四 + 500,這中間任何一步出現問題,整個操作就必須全部復原, 這樣才能保證使用者和銀行都沒有損失

復原

即在事務運作的過程中發生了某種故障,事務不能繼續執行,系統将事務中對資料庫的所有已完成的操作全部撤銷,滾回到事務開始時的狀态

比如:張三轉賬完成後,卧槽,發現轉多了,叫李四退回來,最終張三和李四的錢還是保持原樣,這個就是復原

MYSQL 中可以有兩種方式進行事務的操作:

  • 自動送出事務
  • 手動送出事務

自動送出事務

MySQL 預設每一條 DML(增删改)語句都是一個單獨的事務,每條語句都會自動開啟一個事務,語句執行完畢自動送出事務,MySQL預設是自動送出事務

資料庫事務的四大特性和隔離級别,一文帶你看通透1. 什麼是事務2. 事務的四大特性(ACID)3. 事務的隔離級别

手動送出事務

開啟事務 start transaction; 或者 BEGIN;

送出事務 commit;

復原事務 rollback;

  • 執行成功的情況: 開啟事務 -> 執行多條 SQL 語句 -> 成功送出事務
  • 執行失敗的情況: 開啟事務 -> 執行多條 SQL 語句 -> 事務的復原
資料庫事務的四大特性和隔離級别,一文帶你看通透1. 什麼是事務2. 事務的四大特性(ACID)3. 事務的隔離級别

  如何取消自動送出   MySQL 預設是自動送出事務,如有需要,可 設定為手動送出。設定步驟如下:   1) 登入 mysql ,檢視 autocommit 狀态。          SHOW VARIABLES LIKE 'autocommit' ;  

資料庫事務的四大特性和隔離級别,一文帶你看通透1. 什麼是事務2. 事務的四大特性(ACID)3. 事務的隔離級别

on :自動送出 off : 手動送出   2) 把 autocommit 改成 off;        SET @@autocommit =off;  

資料庫事務的四大特性和隔離級别,一文帶你看通透1. 什麼是事務2. 事務的四大特性(ACID)3. 事務的隔離級别

這樣,以後執行修改操作前就需要開啟事務,執行sql完成,必須送出後才能生效

2. 事務的四大特性(ACID)

原子性(Atomic)

事務是一個不可分割的整體,事務中所有的sql語句要麼全部執行成功,要麼全部失敗

比如:張三轉賬給李四500,需要執行兩條sql,張三賬戶 -500,李四賬戶+500

update account set money = money - 500 where name = '張三';
update account set money = money + 500 where name = '李四';
           

這兩個sql必須全部成功,才能算是真正的轉賬成功,這就是事務的原子性,不可拆分。

一緻性(Consistency)

事務在執行前資料庫的狀态與執行後資料庫的狀态保持一緻

比如:轉賬前2個人的總金額是 2000,中間不管怎麼轉賬,轉幾次賬,轉賬後2個人總金額也是2000。這就是事務的一緻性。

隔離性(Isolation)

一個事務的執行不能被其他事務幹擾。

即一個事務内部的操作及使用的資料,對并發的其他事務是隔離的,并發執行的各個事務之間不能互相幹擾。

這個與事務設定的隔離級别有密切的關系

比如:對于任意兩個并發的事務T1和T2,在事務T1看來,T2要麼在T1開始之前就已經結束,要麼在T1結束之後才開始,這樣每個事務都感覺不到有其他事務在并發地執行。

持久性(Durability)

一旦事務執行成功,對資料庫的修改是持久的。就算關機,資料也是要儲存下來的

比如:我成功修改了資料庫的一條資料後,突然停電了,那條資料依然在資料庫中,永久的儲存着。

3. 事務的隔離級别

資料庫的隔離級别有4種,級别從低到高為:

讀未送出(read uncommitted)、讀已送出(read committed)、可重複讀(repeatable read)、串行化(serializable)

談到事務的隔離級别,首先就得來了解一下事務并發通路産生的問題:髒讀、不可重複讀、幻讀

這幾個問題和資料庫隔離級别密切相關,如果不設定合理的隔離級别,那麼就可能出現這三個并發問題

髒讀:

一個事務讀取到了另一個事務中尚未送出的資料

舉個栗子:

如果目前的資料庫隔離級别為最低——讀未送出(read uncommitted)

假定目前有兩個事務在并發執行,分别是:事務A、事務B

事務A: 張三向李四轉賬500元

對應SQL指令如下(在同一個事務):

update account set money = money - 500 where name = '張三';
update account set money = money + 500 where name = '李四';
           

事務B:李四查詢賬戶餘額

對應SQL指令如下:

SELECT money FROM account where name = '李四';
           

當張三給李四轉賬後(事務A執行,但是事務未送出),

李四此時正好去查詢賬戶(事務B執行),發現賬戶增加了500(此時即發生了髒讀),非常高興。

但是,突然張三突然反悔了,于是迅速復原了事務。

現在李四的賬戶變成了初始的狀态,但是李四讀取出來的金額,卻多了500。

李四啊,别人都還沒送出呢,你就去讀取了,别人萬一復原咋辦,是以你讀的是髒資料,這叫髒讀,是有問題的!

如何解決髒讀問題?

髒讀非常危險的,比如張三向李四購買商品,張三開啟事務,向李四賬号轉入 500 塊,然後打電話給李四說錢 已經轉了。李四一查詢錢到賬了,發貨給張三。張三收到貨後復原事務,李四的再檢視錢沒了。

解決方案:

将全局的隔離級别進行提升為: 讀已送出(read committed)

--  設定事務隔離級别為 read committed

set global transaction isolation level read committed;

重新開啟DOS視窗, 檢視設定是否成功:

-- 檢視事務隔離級别

select @@tx_isolation;

設定完成後,再次重新執行轉賬操作(事務A),不送出,然後李四執行查詢操作(事務B),

内心毫無波瀾,因為賬戶金額還是最初的樣子,說明現在沒有髒讀問題了。

大多數資料庫的預設級别就是read committed,比如Sql Server , Oracle

但是,這種隔離級别還不夠呢,還可能存在不可重複讀的問題。

不可重複讀:

同一個事務中,進行查詢操作,但是每次讀取的資料内容是不一樣的,

這是由于在查詢間隔中,資料被另一個事務修改并送出了

舉個栗子:

如果目前的資料庫隔離級别為 讀已送出(read uncommitted)

假定目前有兩個事務在并發執行,還是事務A、事務B

事務A: 張三向李四轉賬500元

對應SQL指令如下(在同一個事務):

update account set money = money - 500 where name = '張三';
update account set money = money + 500 where name = '李四';
           

事務B:李四查詢賬戶餘額

對應SQL指令如下:

SELECT money FROM account where name = '李四';
           

李四着急忙慌的,先去查詢一次自己的賬戶餘額(事務B),嗯,有1000,不錯。

當張三給李四轉賬成功後(事務A執行并送出),

李四老婆叫他再次确認下自己的餘額,于是李四又去查詢一次自己的賬戶餘額(事務B),我嚓,怎麼和之前查詢不一樣哦,多了500。

我這還處于同一個事務中呢,這讀取結果卻不一樣,到底哪次是對的?

這就是所謂的不可重複讀問題。

下面這張圖很好的诠釋了這個執行過程:

資料庫事務的四大特性和隔離級别,一文帶你看通透1. 什麼是事務2. 事務的四大特性(ACID)3. 事務的隔離級别

如何解決不可重複讀問題?

我們可以考慮這樣一種實際情況:

比如銀行程式需要将查詢結果分别輸出到電腦螢幕和發短信給客 戶,結果在一個事務中針對不同的輸出目的地進行的兩次查詢不一緻,導緻檔案和螢幕中的結果不一緻,銀行從業人員就不知道以哪個為準了

解決方案:

将全局的隔離級别進行提升為:可重複讀(repeatable read) 

-- 設定事務隔離級别為 repeatable read 

set global transaction isolation level repeatable read;

重新開啟DOS視窗, 檢視設定是否成功:

-- 檢視事務隔離級别

select @@tx_isolation;

設定完成後,恢複資料,再次重複上述操作,然後可以發現,李四兩次查詢的金額是相同的,說明已經沒有不可重複讀問題了。

mysql 資料庫的預設級别就是repeatable read

但是,這樣就萬事大吉了嗎,no,還可能存在幻讀問題。

幻讀: 

在同一個事務中,前後兩次查詢同一個範圍的時候,後一次查詢看到了前一次查詢沒有看到的行。

這是由于事務在執行期間,另一個事務新增了資料并送出了

舉個栗子:

假定目前有兩個事務在并發執行,還是事務C、事務D

事務C: 先查詢有沒有id為3的記錄,如果沒有,則進行插入

對應SQL指令如下(在同一個事務):

select * from account where id = 3;
INSERT INTO account VALUES(3,'王五',1000);
           

事務D:插入一條新記錄

對應SQL指令如下:

INSERT INTO account VALUES(3,'王五',1000);
           

執行順序為:

  1)事務C執行第一條SQL, 查詢是否有id為3的記錄

        結果發現,沒有,那我可以插入了。

  2)事務D新增一條記錄,并送出

  3)事務C執行第二條SQL,新增id為3的記錄,發現報錯——主鍵重複

       見鬼了,我剛才讀到的結果應該可以支援我這樣操作才對啊,為什麼現在不可以

select 某記錄是否存在,不存在,準備插入此記錄,但執行 insert 時發現此記錄已存在,無法插入,此時就發生了幻讀。

如何解決幻讀問題?

我們可以考慮這樣一種實際情況:

比如銀行程式需要将查詢結果分别輸出到電腦螢幕和發短信給客 戶,結果在一個事務中針對不同的輸出目的地進行的兩次查詢不一緻,導緻檔案和螢幕中的結果不一緻,銀行工作 人員就不知道以哪個為準了

解決方案:

将事務隔離級别設定到最高:串行化(serializable)

如果一個事務,使用了SERIALIZABLE——可串行化隔離級别時,在這個事務沒有被送出之前 , 

其他的線程,隻能等到目前操作完成之後,才能進行操作,這樣會非常耗時,

而且,影響資料庫的性能,資料庫不會使用這種隔離級别

-- 設定事務隔離級别為 serializable

set global transaction isolation level serializable;

重新開啟DOS視窗, 檢視設定是否成功:

-- 檢視事務隔離級别

select @@tx_isolation;

設定完成後,恢複資料,再次重複上述操作

執行順序為:

   1)事務C執行第一條SQL, 查詢是否有id為3的記錄

        結果發現,沒有,那我可以插入了。

   2)事務D插入一條記錄,這個操作無法完成,光标一直閃爍

   3)事務C執行第二條SQL,新增id為3的記錄, 送出事務 資料插入成功.

   4)事務D在事務C送出之後, 再執行,但是主鍵沖突出現錯誤

這就解決了幻讀問題

tips:

serializable 串行化可以徹底解決幻讀,但是事務隻能排隊執行,嚴重影響效率,資料庫不會使用這種隔離級别

并發問題區分

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

幻讀和不可重複讀的差別:都是讀取了另一條已經送出的事務(這點就髒讀不同),所不同的是不可重複讀針對的是另外事務的update操作,而幻讀針對的是另外事務的insert操作。

總結

  • 髒讀說的是事務知道了自己本不應該知道的東西,強調的動作是查詢,我看到了自己不該看的東西 ;
  • 不可重複讀強調的是一個人查的時候,其他人卻可以增删改, 等我再查的時候,和之前查詢的不一緻,也就是我兩次看到了不一樣的東西;
  • 幻讀說的是我看到了資料是這麼多,其他人又插了一條新的,等我拿剛才的查詢結果再執行操作的時候,冷不丁發現又多了一條資料
資料庫事務的四大特性和隔離級别,一文帶你看通透1. 什麼是事務2. 事務的四大特性(ACID)3. 事務的隔離級别

MySQ如何解決幻讀問題?

既然不設定串行化,MySQL到底怎麼解決幻讀問題?

先說結論,MySQL 存儲引擎 InnoDB 在可重複讀(repeatable read)隔離級别下是解決了幻讀問題的。

方法:

是通過next-key lock在目前讀事務開啟時,

  1. 給涉及到的行加寫鎖(行鎖)防止寫操作;
  2. 給涉及到的行兩端加 間隙鎖(Gap Lock) 防止新增行寫入;進而解決了幻讀問題。

顧名思義,行鎖就是鎖住行的鎖,

間隙鎖,鎖的就是兩個值之間的空隙。比如一個表,初始化插入了 7 個記錄,這就産生了 8 個間隙。

資料庫事務的四大特性和隔離級别,一文帶你看通透1. 什麼是事務2. 事務的四大特性(ACID)3. 事務的隔離級别

由于ID大于7,被間隙鎖(7,+∞)鎖住。這樣就確定了無法再插入新的記錄。

MySQL将行鎖 + 間隙鎖組合統稱為 next-key lock,通過 next-key lock 解決了幻讀問題。

注意

next-key lock的确是解決了幻讀問題,但是next-key lock在并發情況下也經常會造成死鎖。死鎖檢測和處理也會花費時間,一定程度上影響到并發量.

繼續閱讀