一文搞懂MySQL臟讀,幻讀和不可重復讀
MySQL 中事務的隔離
在 MySQL 中事務的隔離級別有以下 4 種:
- 讀未提交(READ UNCOMMITTED)
- 讀已提交(READ COMMITTED)
- 可重復讀(REPEATABLE READ)
- 序列化(SERIALIZABLE)
MySQL 默認的事務隔離級別是可重復讀(REPEATABLE READ),這 4 種隔離級別的說明如下。
1.READ UNCOMMITTED
讀未提交,也叫未提交讀,該隔離級別的事務可以看到其他事務中未提交的數(shù)據(jù)。該隔離級別因為可以讀取到其他事務中未提交的數(shù)據(jù),而未提交的數(shù)據(jù)可能會發(fā)生回滾,因此我們把該級別讀取到的數(shù)據(jù)稱之為臟數(shù)據(jù),把這個問題稱之為臟讀。
2.READ COMMITTED
讀已提交,也叫提交讀,該隔離級別的事務能讀取到已經(jīng)提交事務的數(shù)據(jù),因此它不會有臟讀問題。但由于在事務的執(zhí)行中可以讀取到其他事務提交的結果,所以在不同時間的相同 SQL 查詢中,可能會得到不同的結果,這種現(xiàn)象叫做不可重復讀。
3.REPEATABLE READ
可重復讀,是 MySQL 的默認事務隔離級別,它能確保同一事務多次查詢的結果一致。但也會有新的問題,比如此級別的事務正在執(zhí)行時,另一個事務成功的插入了某條數(shù)據(jù),但因為它每次查詢的結果都是一樣的,所以會導致查詢不到這條數(shù)據(jù),自己重復插入時又失?。ㄒ驗槲ㄒ患s束的原因)。明明在事務中查詢不到這條信息,但自己就是插入不進去,這就叫幻讀 (Phantom Read)。
4.SERIALIZABLE
序列化,事務最高隔離級別,它會強制事務排序,使之不會發(fā)生沖突,從而解決了臟讀、不可重復讀和幻讀問題,但因為執(zhí)行效率低,所以真正使用的場景并不多。?
簡單總結一下,MySQL 的 4 種事務隔離級別對應臟讀、不可重復讀和幻讀的關系如下:
事務隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
---|---|---|---|
讀未提交(READ UNCOMMITTED) | √ | √ | √ |
讀已提交(READ COMMITTED) | × | √ | √ |
可重復讀(REPEATABLE READ) | × | × | √ |
串行化(SERIALIZABLE) | × | × | × |
只看以上概念會比較抽象,接下來,咱們一步步通過執(zhí)行的結果來理解這幾種隔離級別的區(qū)別。
前置知識
1.事務相關的常用命令
# 查看 MySQL 版本 select version(); # 開啟事務 start transaction; # 提交事務 commit; # 回滾事務 rollback;
2.MySQL 8 之前查詢事務的隔離級別
查看全局 MySQL 事務隔離級別和當前會話的事務隔離級別的 SQL 如下:
select @@global.tx_isolation,@@tx_isolation;
以上 SQL 執(zhí)行結果如下圖所示:
3.MySQL 8 之后查詢事務的隔離級別
select @@global.transaction_isolation,@@transaction_isolation;
4.查看連接的客戶端詳情
每個 MySQL 命令行窗口就是一個 MySQL 客戶端,每個客戶端都可以單獨設置(不同的)事務隔離級別,這也是演示 MySQL 并發(fā)事務的基礎。以下是查詢客戶端連接的 SQL 命令:
show processlist;
以上 SQL 執(zhí)行結果如下:
5.查詢連接客戶端的數(shù)量
可以使用以下 SQL 命令,查詢連當前接 MySQL 服務器的客戶端數(shù)量:
show status like 'Threads%';
以上 SQL 執(zhí)行結果如下:
6.設置客戶端的事務隔離級別
通過以下 SQL 可以設置當前客戶端的事務隔離級別:
set session transaction isolation level 事務隔離級別;
事務隔離級別的值有 4 個:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。
7.新建數(shù)據(jù)庫和測試數(shù)據(jù)
創(chuàng)建測試數(shù)據(jù)庫和表信息,執(zhí)行 SQL 如下:
-- 創(chuàng)建數(shù)據(jù)庫 drop database if exists testdb; create database testdb; use testdb; -- 創(chuàng)建表 create table userinfo( id int primary key auto_increment, name varchar(250) not null, balance decimal(10,2) not null default 0 ); -- 插入測試數(shù)據(jù) insert into userinfo(id,name,balance) values(1,'Java',100),(2,'MySQL',200);
創(chuàng)建的表結構和數(shù)據(jù)如下:
8.名稱約定
接下來會使用兩個窗口(兩個客戶端)來演示事務不同隔離級別中臟讀、不可重復讀和幻讀的問題。其中左邊的黑底綠字的客戶端下文將使用“窗口 1”來指代,而右邊的藍底白字的客戶端下文將用“窗口 2”來指代,
如下圖所示:
臟讀
一個事務讀到另外一個事務還沒有提交的數(shù)據(jù),稱之為臟讀。 臟讀演示的執(zhí)行流程如下:
執(zhí)行步驟 | 客戶端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.臟讀演示步驟1
設置窗口 2 的事務隔離級別為讀未提交,設置命令如下:
set session transaction isolation level read uncommitted;
PS:事務隔離級別讀未提交存在臟讀的問題。
然后使用命令來檢查當前連接窗口的事務隔離界別,如下圖所示:
開啟事務并查詢用戶列表信息,如下圖所示:
2.臟讀演示步驟2
在窗口 1 中開啟一個事務,并給 Java 賬戶加 50 元,但不提交事務,執(zhí)行的 SQL 如下:
3.臟讀演示步驟3
在窗口 2 中再次查詢用戶列表,執(zhí)行結果如下:
從上述結果可以看出,在窗口 2 中讀取到了窗口 1 中事務未提交的數(shù)據(jù),這就是臟讀。
4.不可重復讀
不可重復讀是指一個事務先后執(zhí)行同一條 SQL,但兩次讀取到的數(shù)據(jù)不同,就是不可重復讀。
不可重復讀演示的執(zhí)行流程如下:
執(zhí)行步驟 | 客戶端1(窗口1) | 客戶端2(窗口2) | 說明 |
---|---|---|---|
第 1 步 | set session transaction isolation level read committed;start transaction;select * from userinfo; | 設置事務隔離級別為讀已提交;開啟事務;查詢用戶列表,其中 Java 用戶的余額是 100 元。 | |
第 2 步 | start transaction;update userinfo set balance=balance+20 where name='Java';commit; | 開啟事務;給 Java 用戶的余額加 20 元;提交事務。 | |
第 3 步 | select * from userinfo; | 查詢用戶列表,其中 Java 用戶的余額變成了 120 元。 |
窗口 2 同一個事務中的兩次查詢,得到了不同的結果這就是不可重復讀,具體執(zhí)行步驟如下。
5.不可重復讀演示步驟1
設置窗口 2 的事務隔離級別為讀已提交,設置命令如下:
set session transaction isolation level read committed;
PS:讀已提交可以解決臟讀的問題,但存在不可重復讀的問題。
使用命令來檢查當前連接窗口的事務隔離界別,如下圖所示:
在窗口 2 中開啟事務,并查詢用戶表,執(zhí)行結果如下:
此時查詢的列表中,Java 用戶的余額為 100 元。
6.不可重復讀演示步驟2
在窗口 1 中開啟事務,并給 Java 用戶添加 20 元,但不提交事務,再觀察窗口 2 中有沒有臟讀的問題,
具體執(zhí)行結果如下圖所示:
從上述結果可以看出,當把窗口的事務隔離級別設置為讀已提交,已經(jīng)不存在臟讀問題了。 接下來在窗口 1 中提交事務,執(zhí)行結果如下圖所示:
7.不可重復讀演示步驟3
切換到窗口 2 中再次查詢用戶列表,執(zhí)行結果如下:
從上述結果可以看出,此時 Java 用戶的余額已經(jīng)變成 120 元了。在同一個事務中,先后查詢的兩次結果不一致就是不可重復讀。
8.不可重復讀和臟讀的區(qū)別
臟讀可以讀到其他事務中未提交的數(shù)據(jù),而不可重復讀是讀取到了其他事務已經(jīng)提交的數(shù)據(jù),但前后兩次讀取的結果不同。
幻讀
幻讀名如其文,它就像發(fā)生了某種幻覺一樣,在一個事務中明明沒有查到主鍵為 X 的數(shù)據(jù),但主鍵為 X 的數(shù)據(jù)就是插入不進去,就像某種幻覺一樣。
幻讀演示的執(zhí)行流程如下:
執(zhí)行步驟 | 客戶端1(窗口1) | 客戶端2(窗口2) | 說明 |
---|---|---|---|
第 1 步 | set session transaction isolation level repeatable read;start transaction;select * from userinfo where id=3; | 設置事務隔離級別為可重復讀;開啟事務;查詢用戶編號為 3 的數(shù)據(jù),查詢結果為空。 | |
第 2 步 | start transaction;insert into userinfo(id,name,balance) values(3,'Spring',100);commit; | 開啟事務;添加用戶,用戶編號為 3;提交事務。 | |
第 3 步 | insert into userinfo(id,name,balance) values(3,'Spring',100); | 窗口 2 添加用戶編號為 3 的數(shù)據(jù),執(zhí)行失敗。 | |
第 4 步 | select * from userinfo where id=3; | 查詢用戶編號為 3 的數(shù)據(jù),查詢結果為空。 |
具體執(zhí)行結果如下步驟所示!
1.幻讀演示步驟1
設置窗口 2 為可重復讀,可重復有幻讀的問題,查詢編號為 3 的用戶,具體執(zhí)行 SQL 如下:
set session transaction isolation level repeatable read; start transaction; select * from userinfo where id=3;
以上 SQL 執(zhí)行結果如下圖所示:
從上述結果可以看出,查詢的結果中 id=3 的數(shù)據(jù)為空。
2.幻讀演示步驟2
開啟窗口 1 的事務,插入用戶編號為 3 的數(shù)據(jù),然后成功提交事務,執(zhí)行 SQL 如下:
start transaction; insert into userinfo(id,name,balance) values(3,'Spring',100); commit;
以上 SQL 執(zhí)行結果如下圖所示:
3.幻讀演示步驟3
在窗口 2 中插入用戶編號為 3 的數(shù)據(jù),執(zhí)行 SQL 如下:
insert into userinfo(id,name,balance) values(3,'Spring',100);
以上 SQL 執(zhí)行結果如下圖所示:
添加用戶數(shù)據(jù)失敗,提示表中已經(jīng)存在了編號為 3 的數(shù)據(jù),且此字段為主鍵,不能添加多個。
4.幻讀演示步驟4
在窗口 2 中,重新執(zhí)行查詢:
select * from userinfo where id=3;
以上 SQL 執(zhí)行結果如下圖所示:
/ 在此事務中查詢明明沒有編號為 3 的用戶,但插入的時候卻卻提示已經(jīng)存在了,這就是幻讀。
5.不可重復讀和幻讀的區(qū)別
二者描述的則重點不同,不可重復讀描述的側重點是修改操作,而幻讀描述的側重點是添加和刪除操作。
總結
本文演示了 MySQL 的 4 種事務隔離級別:讀未提交(有臟讀問題)、讀已提交(有不可重復讀的問題)、可重復讀(有幻讀的問題)和序列化,其中可重復讀是 MySQL 默認的事務隔離級別。臟讀是讀到了其他事務未提交的數(shù)據(jù),而不可重復讀是讀到了其他事務已經(jīng)提交的數(shù)據(jù),但前后查詢的結果不同,而幻讀則是明明查詢不到,但就是插入不了。
到此這篇關于一文搞懂MySQL臟讀,幻讀和不可重復讀的文章就介紹到這了,更多相關MySQL臟讀內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Linux環(huán)境中使用BIEE 連接SQLServer業(yè)務數(shù)據(jù)源
biee11g默認安裝了mssqlserver的數(shù)據(jù)驅(qū)動,不需要在服務器端進行重新安裝,配置過程主要基于ODBC實現(xiàn),本文主要介紹客戶端為windows、服務端為linux系統(tǒng)的配置過程。2014-07-07數(shù)據(jù)庫中兩張表之間的數(shù)據(jù)同步增加、刪除與更新實現(xiàn)思路
可以采用分別創(chuàng)建增加、刪除、更新的觸發(fā)器(Trigger)來達到兩張表之間數(shù)據(jù)同步的目的,感興趣的朋友可以參考下,希望可以幫助到你2013-03-03日常收集整理SqlServer數(shù)據(jù)庫優(yōu)化經(jīng)驗和注意事項
本文是小編日常收集整理SqlServer數(shù)據(jù)庫優(yōu)化經(jīng)驗和注意事項,詳細介紹了SQL語句優(yōu)化的基本原則,包括索引、查詢和游標的使用等2015-11-11sql server判斷數(shù)據(jù)庫、表、列、視圖是否存在
這篇文章主要介紹了sql server判斷數(shù)據(jù)庫、表、列、視圖是否存在的方法,需要的朋友可以參考下2014-07-07Sql Server使用cursor處理重復數(shù)據(jù)過程詳解
本節(jié)主要介紹了Sql Server cursor的使用,以處理重復數(shù)據(jù)為例,需要的朋友可以參考下2014-08-08SQL語句過濾條件放在on與where子句中的區(qū)別和聯(lián)系淺析
在寫SQL語句的時候,我們經(jīng)常會用到各種表連接,還有各種分組聚合函數(shù),下面這篇文章主要給大家介紹了關于SQL語句過濾條件放在on與where子句中的區(qū)別和聯(lián)系,需要的朋友可以參考下2022-09-09