天天看點

保姆級教程,終于搞懂髒讀、幻讀和不可重複讀了!

我的文章合集:https://gitee.com/mydb/interview

在 MySQL 中事務的隔離級别有以下 4 種:

  1. 讀未送出(READ UNCOMMITTED)
  2. 讀已送出(READ COMMITTED)
  3. 可重複讀(REPEATABLE READ)
  4. 序列化(SERIALIZABLE)

MySQL 預設的事務隔離級别是可重複讀(REPEATABLE READ),這 4 種隔離級别的說明如下。

1.READ UNCOMMITTED

讀未送出,也叫未送出讀,該隔離級别的事務可以看到其他事務中未送出的資料。該隔離級别因為可以讀取到其他事務中未送出的資料,而未送出的資料可能會發生復原,是以我們把該級别讀取到的資料稱之為髒資料,把這個問題稱之為髒讀。

2.READ COMMITTED

讀已送出,也叫送出讀,該隔離級别的事務能讀取到已經送出事務的資料,是以它不會有髒讀問題。但由于在事務的執行中可以讀取到其他事務送出的結果,是以在不同時間的相同 SQL 查詢中,可能會得到不同的結果,這種現象叫做不可重複讀。

3.REPEATABLE READ

可重複讀,是 MySQL 的預設事務隔離級别,它能確定同一事務多次查詢的結果一緻。但也會有新的問題,比如此級别的事務正在執行時,另一個事務成功的插入了某條資料,但因為它每次查詢的結果都是一樣的,是以會導緻查詢不到這條資料,自己重複插入時又失敗(因為唯一限制的原因)。明明在事務中查詢不到這條資訊,但自己就是插入不進去,這就叫幻讀 (Phantom Read)。

4.SERIALIZABLE

序列化,事務最高隔離級别,它會強制事務排序,使之不會發生沖突,進而解決了髒讀、不可重複讀和幻讀問題,但因為執行效率低,是以真正使用的場景并不多。

簡單總結一下,MySQL 的 4 種事務隔離級别對應髒讀、不可重複讀和幻讀的關系如下:

事務隔離級别 髒讀 不可重複讀 幻讀
×
串行化(SERIALIZABLE)

隻看以上概念會比較抽象,接下來,咱們一步步通過執行的結果來了解這幾種隔離級别的差別。

前置知識

1.事務相關的常用指令

# 檢視 MySQL 版本
select version();

# 開啟事務
start transaction;

# 送出事務
commit;

# 復原事務
rollback;
           

2.MySQL 8 之前查詢事務的隔離級别

檢視全局 MySQL 事務隔離級别和目前會話的事務隔離級别的 SQL 如下:

select @@global.tx_isolation,@@tx_isolation;
           

以上 SQL 執行結果如下圖所示:

3.MySQL 8 之後查詢事務的隔離級别

select @@global.transaction_isolation,@@transaction_isolation;
           

4.檢視連接配接的用戶端詳情

每個 MySQL 指令行視窗就是一個 MySQL 用戶端,每個用戶端都可以單獨設定(不同的)事務隔離級别,這也是示範 MySQL 并發事務的基礎。以下是查詢用戶端連接配接的 SQL 指令:

show processlist;
           

以上 SQL 執行結果如下:

5.查詢連接配接用戶端的數量

可以使用以下 SQL 指令,查詢連目前接 MySQL 伺服器的用戶端數量:

show status like 'Threads%';
           

6.設定用戶端的事務隔離級别

通過以下 SQL 可以設定目前用戶端的事務隔離級别:

set session transaction isolation level 事務隔離級别;
           

事務隔離級别的值有 4 個:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。

7.建立資料庫和測試資料

建立測試資料庫和表資訊,執行 SQL 如下:

-- 建立資料庫
drop database if exists testdb;
create database testdb;
use testdb;
-- 建立表
create table userinfo(
  id int primary key auto_increment,
  name varchar(250) not null,
  balance decimal(10,2) not null default 0
);
-- 插入測試資料
insert into userinfo(id,name,balance) values(1,'Java',100),(2,'MySQL',200);
           

建立的表結構和資料如下:

8.名稱約定

接下來會使用兩個視窗(兩個用戶端)來示範事務不同隔離級别中髒讀、不可重複讀和幻讀的問題。其中左邊的黑底綠字的用戶端下文将使用“視窗 1”來指代,而右邊的藍底白字的用戶端下文将用“視窗 2”來指代,如下圖所示:

一個事務讀到另外一個事務還沒有送出的資料,稱之為髒讀。

髒讀示範的執行流程如下:

執行步驟 用戶端1(視窗1) 用戶端2(視窗2) 說明
第 1 步 set session transaction isolation level read uncommitted;start transaction;select * from userinfo; 設定事務隔離級别為讀未送出;開啟事務;查詢使用者清單,其中 Java 使用者的餘額為 100 元。
第 2 步 start transaction;update userinfo set balance=balance+50 where name='Java'; 開啟事務;給 Java 使用者的賬戶加 50 元;
第 3 步 select * from userinfo; 查詢使用者清單,其中 Java 使用者的餘額變成了 150 元。

髒讀示範步驟1

設定視窗 2 的事務隔離級别為讀未送出,設定指令如下:

set session transaction isolation level read uncommitted;
           
PS:事務隔離級别讀未送出存在髒讀的問題。

然後使用指令來檢查目前連接配接視窗的事務隔離界别,如下圖所示:

開啟事務并查詢使用者清單資訊,如下圖所示:

髒讀示範步驟2

在視窗 1 中開啟一個事務,并給 Java 賬戶加 50 元,但不送出事務,執行的 SQL 如下:

髒讀示範步驟3

在視窗 2 中再次查詢使用者清單,執行結果如下:

從上述結果可以看出,在視窗 2 中讀取到了視窗 1 中事務未送出的資料,這就是髒讀。

不可重複讀是指一個事務先後執行同一條 SQL,但兩次讀取到的資料不同,就是不可重複讀。

不可重複讀示範的執行流程如下:

set session transaction isolation level read committed;start transaction;select * from userinfo; 設定事務隔離級别為讀已送出;開啟事務;查詢使用者清單,其中 Java 使用者的餘額是 100 元。
start transaction;update userinfo set balance=balance+20 where name='Java';commit; 開啟事務;給 Java 使用者的餘額加 20 元;送出事務。
查詢使用者清單,其中 Java 使用者的餘額變成了 120 元。

視窗 2 同一個事務中的兩次查詢,得到了不同的結果這就是不可重複讀,具體執行步驟如下。

不可重複讀示範步驟1

設定視窗 2 的事務隔離級别為讀已送出,設定指令如下:

set session transaction isolation level read committed;
           
PS:讀已送出可以解決髒讀的問題,但存在不可重複讀的問題。

使用指令來檢查目前連接配接視窗的事務隔離界别,如下圖所示:

在視窗 2 中開啟事務,并查詢使用者表,執行結果如下:

此時查詢的清單中,Java 使用者的餘額為 100 元。

不可重複讀示範步驟2

在視窗 1 中開啟事務,并給 Java 使用者添加 20 元,但不送出事務,再觀察視窗 2 中有沒有髒讀的問題,具體執行結果如下圖所示:

從上述結果可以看出,當把視窗的事務隔離級别設定為讀已送出,已經不存在髒讀問題了。

接下來在視窗 1 中送出事務,執行結果如下圖所示:

不可重複讀示範步驟3

切換到視窗 2 中再次查詢使用者清單,執行結果如下:

從上述結果可以看出,此時 Java 使用者的餘額已經變成 120 元了。在同一個事務中,先後查詢的兩次結果不一緻就是不可重複讀。

不可重複讀和髒讀的差別

髒讀可以讀到其他事務中未送出的資料,而不可重複讀是讀取到了其他事務已經送出的資料,但前後兩次讀取的結果不同。

幻讀名如其文,它就像發生了某種幻覺一樣,在一個事務中明明沒有查到主鍵為 X 的資料,但主鍵為 X 的資料就是插入不進去,就像某種幻覺一樣。

幻讀示範的執行流程如下:

set session transaction isolation level repeatable read;start transaction;select * from userinfo where id=3; 設定事務隔離級别為可重複讀;開啟事務;查詢使用者編号為 3 的資料,查詢結果為空。
start transaction;insert into userinfo(id,name,balance) values(3,'Spring',100);commit; 開啟事務;添加使用者,使用者編号為 3;送出事務。
insert into userinfo(id,name,balance) values(3,'Spring',100); 視窗 2 添加使用者編号為 3 的資料,執行失敗。
第 4 步 select * from userinfo where id=3; 查詢使用者編号為 3 的資料,查詢結果為空。

具體執行結果如下步驟所示。

幻讀示範步驟1

設定視窗 2 為可重複讀,可重複有幻讀的問題,查詢編号為 3 的使用者,具體執行 SQL 如下:

set session transaction isolation level repeatable read;
start transaction;
select * from userinfo where id=3;
           

從上述結果可以看出,查詢的結果中 id=3 的資料為空。

幻讀示範步驟2

開啟視窗 1 的事務,插入使用者編号為 3 的資料,然後成功送出事務,執行 SQL 如下:

start transaction;
insert into userinfo(id,name,balance) values(3,'Spring',100);
commit;
           

幻讀示範步驟3

在視窗 2 中插入使用者編号為 3 的資料,執行 SQL 如下:

insert into userinfo(id,name,balance) values(3,'Spring',100);
           

添加使用者資料失敗,提示表中已經存在了編号為 3 的資料,且此字段為主鍵,不能添加多個。

幻讀示範步驟4

在視窗 2 中,重新執行查詢:

select * from userinfo where id=3;
           

/

在此事務中查詢明明沒有編号為 3 的使用者,但插入的時候卻卻提示已經存在了,這就是幻讀。

不可重複讀和幻讀的差別

二者描述的則重點不同,不可重複讀描述的側重點是修改操作,而幻讀描述的側重點是添加和删除操作。

總結

本文示範了 MySQL 的 4 種事務隔離級别:讀未送出(有髒讀問題)、讀已送出(有不可重複讀的問題)、可重複讀(有幻讀的問題)和序列化,其中可重複讀是 MySQL 預設的事務隔離級别。髒讀是讀到了其他事務未送出的資料,而不可重複讀是讀到了其他事務已經送出的資料,但前後查詢的結果不同,而幻讀則是明明查詢不到,但就是插入不了。

是非審之于己,毀譽聽之于人,得失安之于數。

公衆号:Java面試真題解析

關注下面二維碼,訂閱更多精彩内容。

保姆級教程,終于搞懂髒讀、幻讀和不可重複讀了!
保姆級教程,終于搞懂髒讀、幻讀和不可重複讀了!
保姆級教程,終于搞懂髒讀、幻讀和不可重複讀了!

關注公衆号(加好友):

保姆級教程,終于搞懂髒讀、幻讀和不可重複讀了!

作者:

王磊的部落格

出處:

http://vipstone.cnblogs.com/