Mysql數據庫中事物的四種隔離級別

目錄


數據庫的四種隔離級別


前言

首先要推薦這個數據庫的文章,感覺寫的真好:
如果有人問你數據庫的原理,叫他看這篇文章


簡介

引用原文中關於數據庫四種隔離級別的介紹。

現代數據庫不會使用純粹的隔離作爲默認模式,因爲它會帶來巨大的性能消耗。SQL一般定義4個隔離級別:

  • 串行化(Serializable,SQLite默認模式):最高級別的隔離。兩個同時發生的事務100%隔離,每個事務有自己的『世界』。

  • 可重複讀(Repeatable read,MySQL默認模式):每個事務有自己的『世界』,除了一種情況。如果一個事務成功執行並且添加了新數據,這些數據對其他正在執行的事務是可見的。但是如果事務成功修改了一條數據,修改結果對正在運行的事務不可見。所以,事務之間只是在新數據方面突破了隔離,對已存在的數據仍舊隔離。
    舉個例子,如果事務A運行”SELECT count(1) from TABLE_X” ,然後事務B在 TABLE_X 加入一條新數據並提交,當事務A再運行一次 count(1)結果不會是一樣的。
    這叫幻讀(phantom read)。

  • 讀取已提交(Read committed,Oracle、PostgreSQL、SQL Server默認模式):可重複讀+新的隔離突破。如果事務A讀取了數據D,然後數據D被事務B修改(或刪除)並提交,事務A再次讀取數據D時數據的變化(或刪除)是可見的。
    這叫不可重複讀(non-repeatable read)。

  • 讀取未提交(Read uncommitted):最低級別的隔離,是讀取已提交+新的隔離突破。如果事務A讀取了數據D,然後數據D被事務B修改(但並未提交,事務B仍在運行中),事務A再次讀取數據D時,數據修改是可見的。如果事務B回滾,那麼事務A第二次讀取的數據D是無意義的,因爲那是事務B所做的從未發生的修改(已經回滾了嘛)。
    這叫髒讀(dirty read)。

多數數據庫添加了自定義的隔離級別(比如 PostgreSQL、Oracle、SQL Server的快照隔離),而且並沒有實現SQL規範裏的所有級別(尤其是讀取未提交級別)。

默認的隔離級別可以由用戶/開發者在建立連接時覆蓋(只需要增加很簡單的一行代碼)。


實驗

看到這個介紹簡直也是一頭霧水。直接做實驗來理解吧。


0#準備工作

建立一個balance表,裏面有一列數據money,咱們就玩id=1這一行的money,初始值爲2960。

然後打開兩個遠程登錄的窗口,都打開MySQL數據庫,開始實驗。

從隔離級別低到高,分別是:讀取未提交、讀取已提交、可重複讀、串行化。


1#讀取未提交

  • 查看窗口1的會話隔離級別,發現MySQL的默認隔離級別是Repeatable read。
select @@tx_isolation;
  • 1

MySQL默認隔離級別

  • 設置會話隔離級別爲實驗的read uncommitted:
set session transaction isolation level read uncommitted;
  • 1

設置隔離級別

  • 在窗口1開始一個事務並查看money。
-- 窗口1
begin;
select money from balance where id = 1;
  • 1
  • 2
  • 3

這裏寫圖片描述

  • 然後在窗口2開始一個事務並修改money。
-- 窗口2
begin;
update balance set money = money - 1000 where id = 1;
select money from balance where id = 1;
  • 1
  • 2
  • 3
  • 4

這裏寫圖片描述

可見在窗口2這個事務中money已經被修改,但是還沒提交。

  • 回到窗口1,再查看money
-- 窗口1
select money from balance where id = 1;
  • 1
  • 2

這裏寫圖片描述

可見,雖然窗口2的事務還沒提交,但是窗口1的事務已經可以讀到還沒提交的數據,所以這就叫做 讀取未提交 。可以看到,兩個事務的隔離性很低,這是四種隔離級別中最低的級別。

  • 那麼,如果窗口2的事務發生錯誤,將數據回滾,money變回原來的值,實際上money不應該發生變化,可是咱們的窗口1的事務還是讀到了 錯誤回滾前 的1960,這就叫 髒讀
-- 窗口2
rollback;
select money from balance where id = 1;
commit
  • 1
  • 2
  • 3
  • 4

這裏寫圖片描述

money變回2960了。

  • 所以,如果窗口2的事務代表轉賬,money從2960轉走1000變爲1960,然後轉賬出錯,回滾回2960。
  • 數據庫的隔離級別如果是read uncommitted的話,其他的事務(窗口1 的事務)就有可能再中間讀到1960這個錯誤值。這就叫 髒讀 啊親。

2#讀取已提交

  • 先將窗口1的事務結束掉(commit),然後設置隔離級別爲read committed。再開始一個新事務,讀取money。
-- 窗口1
commit;
set session transaction isolation level read committed;
begin;
select money from balance where id = 1;
  • 1
  • 2
  • 3
  • 4
  • 5

這裏寫圖片描述

  • 窗口2開始新事務修改money
-- 窗口2
begin;
update balance set money = money - 1000;
select money from balance where id = 1;
  • 1
  • 2
  • 3
  • 4

這裏寫圖片描述

  • 回到窗口1,再讀money
-- 窗口1
select money from balance where id = 1;
  • 1
  • 2

這裏寫圖片描述

  • 哈哈,這回窗口1中的money沒被修改了吧。

  • 然後將窗口2的修改提交。

-- 窗口2
commit;
select money from balance where id = 1;
  • 1
  • 2
  • 3

這裏寫圖片描述

  • 回到窗口1再讀money,可以讀到已提交的money了。
-- 窗口1
select money from balance where id = 1;
commit;
  • 1
  • 2
  • 3

這裏寫圖片描述

  • 窗口1的事務可以讀到窗口2的已提交的事務,這就叫 讀取已提交

3#可重複讀

  • 所以如果要再提高隔離性,那是怎麼樣呢?那就是窗口2的事務就算提交了數據修改,我窗口1的事務也不管,還是讀取到原來的數據。
-- 窗口1
-- 設置隔離級別爲repeatable read
set session transaction isolation level repeatable read;
begin;
select money from balance where id = 1;
  • 1
  • 2
  • 3
  • 4
  • 5

這裏寫圖片描述

-- 窗口2
-- 修改money並提交
begin;
update balance set money = money - 1000;
select money from balance where id = 1;
commit;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

這裏寫圖片描述

-- 窗口1
-- 窗口1的事務還是視而不見。
select money from balance where id = 1;
  • 1
  • 2
  • 3
  • 4

這裏寫圖片描述

  • 窗口2的事務已經提交了,數據庫的數據money已經真正被修改了,可是窗口1的事務還是視而不見,仍然隔離了,讀取的數值仍然不變,重複讀的數值不會變,這就是 可重複讀

4#串行化

  • 難道還有更變態的更強的隔離級別,答案是肯定的,那就是 串行化 。串行化是怎麼再增強隔離性的呢?回到一開始文章中對 可重複讀串行化 的解釋。
  • 可重複讀:

    每個事務有自己的『世界』,除了一種情況。如果一個事務成功執行並且添加了新數據,這些數據對其他正在執行的事務是可見的。但是如果事務成功修改了一條數據,修改結果對正在運行的事務不可見。所以,事務之間只是在新數據方面突破了隔離,對已存在的數據仍舊隔離。

  • 串行化:

    最高級別的隔離。兩個同時發生的事務100%隔離,每個事務有自己的『世界』。

  • 也就是說,如果是在可重複讀的情況下,插入新數據這個事情是沒有被隔離的,但是在串行化的情況下,插入新數據也被隔離了。好吧,還是很抽象,還是實驗最好。

-- 窗口1
-- 先是繼續用可重複讀的隔離級別。
begin;
select count(*) from balance;
  • 1
  • 2
  • 3
  • 4
  • 5

這裏寫圖片描述

好的,表裏有579條數據。然後我們在窗口2的事務中插入新數據。

-- 窗口2
-- 在窗口2的事務中插入新數據。
begin;
insert into balance (money) values (999);
commit;
select count(*) from balance;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

這裏寫圖片描述

好了,數據表中的記錄數已經達到了580條。那麼回到窗口1,按照可重複讀的定義,應該是580條記錄,然後串行化的設置纔是579條記錄。可是!
這裏寫圖片描述

  • 真是萬萬沒想到啊,這不是打自己臉嗎,啪啪響。上網搜了一下,找到這麼一句話:

REPEATABLE READ:在mysql中,不會出現幻讀。mysql的實現和標準定義的RR隔離級別有差別。

好吧有興趣的同學可以看看這位大神的詳細解釋:MySQL_REPEATABLE-READ事務隔離級別 && 幻讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章