MySql的事務隔離級別

来源:https://www.cnblogs.com/hello-shf/archive/2019/04/13/10702316.html
-Advertisement-
Play Games

一、事務的四大特性(ACID) 瞭解事務隔離級別之前不得不瞭解的事務的四大特性。 1、原子性(Atomicity) 事務開始後所有操作,要麼全部做完,要麼全部不做。事務是一個不可分割的整體。事務在執行過程中出錯,會回滾到事務開始之前的狀態,以此來保證事務的完整性。類似於原子在物理上的解釋:指化學反應 ...


一、事務的四大特性(ACID)

瞭解事務隔離級別之前不得不瞭解的事務的四大特性。

1、原子性(Atomicity)

事務開始後所有操作,要麼全部做完,要麼全部不做。事務是一個不可分割的整體。事務在執行過程中出錯,會回滾到事務開始之前的狀態,以此來保證事務的完整性。類似於原子在物理上的解釋:指化學反應不可再分的基本微粒,原子在化學反應中不可分割 。

2、一致性(Consistency)

事務在開始和結束後,能保證資料庫完整性約束的正確性即數據的完整性。比如經典的轉賬案例,A向B轉賬,我們必須保證A扣了錢,B一定能收到錢。個人理解類似於物理上的能量守恆。

3、隔離性(Isolation)

事務之間的完全隔離。比如A向一張銀行卡轉賬,避免在同一時間過多的操作導致賬戶金額的缺損,所以在A轉入結束之前是不允許其他針對此卡的操作的。

4、持久性(Durability)

事務的對數據的影響是永久性的。通俗的解釋為事務完成後,對數據的操作都要進行落盤(持久化)。事務一旦完成就是不可逆的,在資料庫的操作上表現為事務一旦完成就是無法回滾的。

二、事務併發問題

在互聯網的大潮中,程式存在的價值早已不是在傳統行業中為了幫人們解決一些複雜的業務邏輯。用戶體驗至上的互聯網時代,代碼就像西二旗地鐵站碼農的腳步一樣,速度、速度、還是速度。當然也不能坐錯了方向,本來想去西直門最後到了東直門(暫且理解為正確性吧)。相對於傳統行業複雜的業務邏輯,互聯網更註重併發帶給程式的速度與激情。當然超速也是有代價的。在併發事務中,一不小心可憐的碼農就要跑路了。

1、臟讀

又稱無效數據讀出。一個事務讀取另外一個事務還沒有提交的數據叫臟讀。

例如:事務T1修改了一行數據,但是還沒有提交,這時候事務T2讀取了被事務T1修改後的數據,之後事務T1因為某種原因Rollback了,那麼事務T2讀取的就是臟數據。

2、不可重覆讀

同一個事務中,多次讀出的同一數據是不一致的。

例如:事務T1讀取某一數據,事務T2讀取並修改了該數據,T1為了對讀取值進行檢驗而再次讀取該數據,便得到了不同的結果。

3、幻讀

不好表述直接上例子吧:

在倉庫管理中,管理員要給剛到的一批商品進入庫管理,當然入庫之前肯定是要查一下之前有沒有入庫記錄,確保正確性。管理員A確保庫中不存在該商品之後給該商品進行入庫操作,假如這時管理員B因為手快將已將該商品進行了入庫操作。這時管理員A發現該商品已經在庫中。就像剛剛發生了幻讀一樣,本來不存在的東西,突然之間他就有了。

註:三種問題看似不太好理解,臟讀側重的是數據的正確性。不可重覆度側重的於對數據的修改,幻讀側重於數據的新增和刪除。

三、MySql四種事務隔離級別

上一章節瞭解了高併發下對事務的影響。事務的四種隔離級別就是對以上三種問題的解決方案。

隔離級別 臟讀        不可重覆度 幻讀     
讀未提交(read-uncommitted)
不可重覆讀(read-committed)
可重覆讀(repeatable-read)
可串列化(serializable)

四、sql演示四種隔離級別

mysql版本:5.6

存儲引擎:InnoDB

工具:navicat

建表語句:

CREATE TABLE `tb_bank` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(16) COLLATE utf8_bin DEFAULT NULL,
  `account` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (1, '小明', 1000);

1、通過sql演示------read-uncommitted的臟讀

(2)read-uncommit導致的臟讀

所謂臟讀就是說,兩個事務,其中一個事務能讀取到另一個事務未提交的數據。
場景:session1要轉出200元,session2轉入100元。基數為1000。順利完成正確的結果應該是900元。但是我們假設session2轉入因為某種原因事務回滾。這時正確的結果應該是800元。

演示步驟:
① 新建兩個session(會話,在navicat中表現為兩個查詢視窗,在mysql命令行中也是兩個視窗),分別執行

1 select @@tx_isolation;//查詢當前事務隔離級別
2 set session transaction isolation level read uncommitted;//將事務隔離級別設置為 讀未提交

 ② 兩個session都開啟事務

1 start transaction;//開啟事務

③ session1和session2:證明兩個操作執行前賬戶餘額為1000

1 select * from tb_bank where id=1;//查詢結果為1000

④ session2:此時假設session2的更新先執行。

1 update tb_bank set account = account + 100 where id=1;

⑤ session1:在session2 commit之前session1開始執行。

1 select * from tb_bank where id=1;//查詢結果:1100

⑥ session2:因為某種原因,轉入失敗,事務回滾。

1 rollback;//事務回滾
2 commit;//提交事務

⑦  這時session1開始轉出,並且session1覺得⑤中查詢結果1100就是正確的數據。

1 update tb_bank set account=1100-200 where id=1;
2 commit;

⑧ session1 和 session2查詢結果

1 select * from tb_bank where id=1;//查詢結果:900

這時我們發現因為session1的臟讀造成了最終數據不一致。正確的結果應該為800;
到此我們怎麼避免臟讀呢,將事務的隔離性增加一個級別到read-commit

(2)read-commit解決臟讀

重置數據,使數據恢復到account=1000

① 新建兩個session,分別設置

1 set session transaction isolation level read committed;//將隔離級別設置為 不可重覆讀

重覆執行(1)中的②③④步

 ⑤ session1執行查詢

1 select * from tb_bank where id=1;//查詢結果為1000,這說明 不可重覆讀 隔離級別有效的隔離了兩個會話的事務。

這時我們發現,將事務的隔離升級為read-committed;後有效的隔離了兩個事務,使得session1中的事務無法查詢到session2中事務對數據的改動。有效的避免了臟讀。

 2、通過sql演示-----read-committed的不可重覆讀

(1)read-commit的不可重覆讀

重置數據,使數據恢復到account=1000

所謂的不可重覆讀就是說,一個事務不能讀取到另一個未提交的事務的數據,但是可以讀取到提交後的數據。這個時候就造成了兩次讀取的結果不一致了。所以說是不可重覆讀。
READ COMMITTED 隔離級別下,每次讀取都會重新生成一個快照,所以每次快照都是最新的,也因此事務中每次SELECT也可以看到其它已commit事務所作的更改
場景:session1進行賬戶的查詢,session2進行賬戶的轉入100。
session1開啟事務準備對賬戶進行查詢然後更新,這時session2也對該賬戶開啟了事務進行更新。正確的結果應該是在session1開啟事務以後查詢讀到的結果應該是一樣的。

① 新建兩個session,分別設置

1 set session transaction isolation level read committed;

② session1和session2分別開啟事務

1 start transaction;

③ session1第一次查詢:

1 select * from tb_bank where id=1;//查詢結果:1000

④ session2進行更新:

1 update tb_bank set account = account+100 where id=1;
2 select * from tb_bank where id=1;//查詢結果:1100

⑤ session1第二次查詢:

1 select * from tb_bank where id=1;//查詢結果:1100。和③中查詢結果對比,session1兩次查詢結果不一致。

查看查詢結果可知,session1在開啟事務期間發生重覆讀結果不一致,所以可以看到read commit事務隔離級別是不可重覆讀的。顯然這種結果不是我們想要的。

(2)repeatable-read可重覆讀

重置數據,使數據恢復到account=1000

① 新建兩個session,分別設置

1 set session transaction isolation level repeatable read;

重覆(1)中的②③④
⑤ session1第二次查詢:

1 select * from tb_bank where id=1;//查詢結果為:1000

從結果可知,repeatable-read的隔離級別下,多次讀取結果是不受其他事務影響的。是可重覆讀的。到這裡產生了一個疑問,那session1在讀到的結果中依然是session2更新前的結果,那session1中繼續轉入100能得到正確的1200的結果嗎?
繼續操作:
⑥ session1轉入100:

1 update tb_bank set account=account+100 where id=1;

到這裡感覺自己被騙了,鎖,鎖,鎖。session1的更新語句被阻塞了。只有session2中的update語句commit之後,session1中才能繼續執行。session的執行結果是1200,這時發現session1並不是用1000+100計算的,因為可重覆讀的隔離級別下使用了MVCC機制,select操作不會更新版本號,是快照讀(歷史版本)。insert、update和delete會更新版本號,是當前讀(當前版本)。

3、通過sql演示-----repeatable-read的幻讀

在業務邏輯中,通常我們先獲取資料庫中的數據,然後在業務中判斷該條件是否符合自己的業務邏輯,如果是的話,那麼就可以插入一部分數據。但是mysql的快照讀可能在這個過程中會產生意想不到的結果。
場景模擬:
session1開啟事務,先查詢有沒有小張的賬戶信息,沒有的話就插入一條。這是session2也執行和session1同樣的操作。

準備工作:插入兩條數據

1 INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (2, '小紅', 800);
2 INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (3, '小磊', 6000);

(1)repeatable-read的幻讀

① 新建兩個session都執行

1 set session transaction isolation level repeatable read;
2 start transaction;
3 select * from tb_bank;//查詢結果:(這一步很重要,直接決定了快照生成的時間)

結果都是:


② session2插入數據

1 INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (4, '小張', 8000);
2 select * from tb_bank;

結果數據插入成功。此時session2提交事務

1 commit;

③ session1進行插入
插入之前我們先看一下當前session1是否有id=4的數據

1 select * from tb_bank;

結果session1中沒有該條記錄,這時按照我們通常的業務邏輯,此時應該是能成功插入id=4的數據。繼續執行:

1 INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (4, '小張', 8000);

結果插入失敗,提示該條已經存在,但是我們查詢裡面並沒有這一條數據啊。為什麼會插入失敗呢?

因為①中的select語句生成了快照,之後的讀操作(未加讀鎖)都是進行的快照讀,即在當前事務結束前,所有的讀操作的結果都是第一次快照讀產生的快照版本。疑問又來了,為什麼②步驟中的select語句讀到的不是快照版本呢?因為update語句會更新當前事務的快照版本。具體參閱第五章節。

(2)repeatable-read利用當前讀解決幻讀

重覆(1)中的①②
③ session1進行插入
插入之前我們先看一下當前session1是否有id=4的數據

1 select * from tb_bank;

結果session1中沒有該條記錄,這時按照我們通常的業務邏輯,此時應該是能成功插入id=4的數據。

1 select * from tb_bank lock in share mode;//採用當前讀

結果:發現當前結果中已經有小張的賬戶信息了,按照業務邏輯,我們就不在繼續執行插入操作了。
這時我們發現用當前讀避免了repeatable-read隔離級別下的幻讀現象。

4、serializable隔離級別

在此級別下我們就不再做serializable的避免幻讀的sql演示了,畢竟是給整張表都加鎖的。

五、當前讀和快照讀

本想把當前讀和快照讀單開一片博客,但是為了把幻讀總結明白,暫且在本章節先簡單解釋下快照讀和當前讀。後期再追加一篇MVCC,next-key的博客吧。。。

1、快照讀:即一致非鎖定讀。

① InnoDB存儲引擎下,查詢語句預設執行快照讀。

② RR隔離級別下一個事務中的第一次讀操作會產生數據的快照。

③ update,insert,delete操作會更新快照。

 

四種事務隔離級別下的快照讀區別:

① read-uncommitted和read-committed級別:每次讀都會產生一個新的快照,每次讀取的都是最新的,因此RC級別下select結果能看到其他事務對當前數據的修改,RU級別甚至能讀取到其他未提交事務的數據。也因此這兩個級別下數據是不可重覆讀的。

② repeatable-read級別:基於MVCC的併發控制,併發性能極高。第一次讀會產生讀數據快照,之後在當前事務中未發生快照更新的情況下,讀操作都會和第一次讀結果保持一致。快照產生於事務中,不同事務中的快照是完全隔離的。

③ serializable級別:從MVCC併發控制退化為基於鎖的併發控制。不區別快照讀與當前讀,所有的讀操作均為當前讀,讀加讀鎖 (S鎖),寫加寫鎖 (X鎖)。Serializable隔離級別下,讀寫衝突,因此併發度急劇下降。(鎖表,不建議使用)

2、當前讀:即一致鎖定讀。

如何產生當前讀

① select ... lock in share mode

② select ... for update

③ update,insert,delete操作都是當前讀。

 

讀取之後,還需要保證當前記錄不能被其他併發事務修改,需要對當前記錄加鎖。①中對讀取記錄加S鎖 (共用鎖),②③X鎖 (排它鎖)。

3、疑問總結

① update,insert,delete操作為什麼都是當前讀?

簡單來說,不執行當前讀,數據的完整性約束就有可能遭到破壞。尤其在高併發的環境下。

分析update語句的執行步驟:update table set ... where ...;

InnoDB引擎首先進行where的查詢,查詢到的結果集從第一條開始執行當前讀,然後執行update操作,然後當前讀第二條數據,執行update操作......所以每次執行update都伴隨著當前讀。delete也是一樣,畢竟要先查到該數據才能刪除。insert有點不同,insert操作執行前需要執行唯一鍵的檢查。補充一句:InnoDB引擎一定存在一個唯一鍵,後面關於聚簇索引的博客會繼續講解。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • (1)sed: cat file | sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' (2)tr: cat file | tr a-z A-Z cat file | tr "[:lower:]" "[:upper:]" ( ...
  • $ sudo awk 'NR%2==1{close(p".txt");++p}{print > p".txt"}' test.txt $ sudo split -d -l 2 test.txt new ...
  • 1.新建目錄,clone源碼 2.添加配置,vim /etc/vim/vimrc ...
  • 方法一:暫時修改機器名:hostname 用hostname命令可以臨時修改機器名,但機器重新啟動之後就會恢複原來的值。 #hostname //查看機器名 #hostname -i //查看本機器名對應的ip地址 方法二:永久性修改機器名:修改系統配置文件 修改/etc/sysconfig/net ...
  • 1.統計本目錄下除`./apps/myapp/migrations`的所有py文件 2.單個文件行數 ...
  • 要安裝centos系統,就必須得有centos系統軟體安裝程式,可以通過瀏覽器訪問centos官網http://www.centos.org,然後找到Downloads - > mirrors鏈接,點擊後進入下載,但是由於這是國外的網址,下載速度肯定受限。 因此可以使用國內的鏡像源 下載安裝激活vm ...
  • 使用RouterOS, 搭建虛擬路由器,並且配置多個網關互通。配置步驟如下。 基礎配置 1. RouterOS 伺服器,設置如下 2. VM 不同網段的設置 == 192.168.4.X 網段 設置gateway為RouterOS伺服器上的對應網段的IP = 192.168.4.1 然後,記得重啟n ...
  • 概述 NewSQL日漸火熱,無論還是開源的TiDB,CockroachDB還是互聯網大廠的Spanner,Oceanbase都號稱NewSQL,也就是分散式資料庫。NewSQL的典型特征就是,支持SQL,支持事務,高性能,低成本,高可靠,強一致,易擴展,運維友好等。從NewSQL的演進來看,所謂Ne ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...