MySQL資料庫事務各隔離級別加鎖情況--read uncommitt

25minutes發表於2021-09-09
1.目的

1.1 合適人群

1.資料庫事務特徵我只是背過,並沒有很深刻的理解。
2.資料庫事務的隔離級別只是瞭解,並沒有深刻理解,也沒有在實際工作中體驗使用過。
3.經常面試被人問起資料庫加鎖情況,一頭霧水,很懵。
4.在網上找過很多部落格,有的寫得太多沒耐心看,有的寫得摘抄的定義,泛泛而談,沒有實操更沒有講解。

1.2 關於這篇分享對以上問題的解決

1.實踐出真知,如果認真讀完,並實操,實操過後反覆咀嚼,相信上面的問題,除了你有沒有耐心看等主觀因素,其他的都能一一解決。
2.希望這是理解資料庫事務問題的一篇好文章。
3.如果有什麼問題,請評論下, 我們多交流謝謝。
2.事務本質剖析
2.1 什麼是事務?

2.2.1 如下表格所示:

事務類別(不考慮分散式事物) 事務本質 併發事務解決方案 併發事務方案解決的問題 併發事務解決方案實現原理
資料庫事務(狹義理解) 資料庫sql執行過程 控制事務隔離級別 確保資料完整、安全、一致性,在此基礎上實現高效能訪問(魚和熊掌不可兼得) 不同的加鎖策略
應用層事務(廣義理解) 業務邏輯 制定多執行緒訪問策略,如悲觀鎖(同步)、樂觀鎖(無鎖,CAS思想) 確保執行緒之間操作不會相互影響,保證各訪問能保證得到期望結果,並在此基礎上實現最大可能性的高效能訪問 不同的加鎖策略

2.2.2 對上述表格內容的解釋

##msyql事務
1.mysql:傳統理解 mysql 中的一次操作過程(sql 執行)是一次事務。
2.mysql:那麼多個執行緒 同時操作 mysql 中的資料(同一條資料,一個範圍內資料)就叫併發事務。
3.mysql:資料庫層面使用不同的事務隔離級別來進行併發事務的控制,不同的隔離級別是因為資料庫中內部鎖機制的使用方式不同,例如有的是在select完成之後立馬釋放鎖,有的是在整個事務commit 之後釋放鎖
。
--------------------------------------------------------------------------------------------------------------
##應用層事務
1.應用:其實每一個執行緒呼叫服務本質上也是事務。
2.應用:多個執行緒同時呼叫服務,叫併發呼叫服務,也可以叫併發事務。
3.應用:應用層應對併發事務(訪問)解決方案有同步(悲觀鎖)、樂觀鎖(無鎖CAS)。我們對併發訪問做系統應用層控制也會使用到鎖。

個人理解這就是事務的本質。事務不應該只僅限於資料庫。

2.2 關於ACID

舉例子說明

1.A 原子性:事務可以簡單理解為一次資料庫操作,也就是執行sql的過程,要麼執行,要麼不執行,整個執行結果只有兩種執行成功,執行失敗。
2.C 一致性:A有100塊錢,轉1塊錢給另外一個帳戶,還有99塊錢,在整個事務執行過程中,錢數總是100塊,不會變,這就是一致性。
3.I 隔離性:事務執行過程相互隔離,不會相互之間產生影響(這只是美好的願望)。意思是多個事務併發執行的話,結果應該與多個事務序列執行效果是一樣的。但併發情況下需要考慮效能,所以就需要在隔離性上做些手腳(妥協),也就是制定不同的隔離級別達到不同的併發效能。
4.D 永續性:事務每一次的執行結果都應該持久化(儲存)到資料庫中(磁碟資料)。想想除了select,其他的update/delete/insert都會產生這樣的結果,持久化在應用場景中是必須的,除非你寫了假介面。哈哈。
3.資料庫事務的隔離級別

3.1 為什麼需要隔離級別?

1.四個特性之隔離性的體現。
2.對不同併發事務應用場景提供不同解決方案。解決方案本質,加鎖。
3.如果不需要隔離別會出現什麼情況?
   假設一個場景,資料庫中任何資料在被併發 curd 時不設定隔開級別,也就是不加鎖,情景平移,我們學習多執行緒時,對執行緒對公共變數的併發操作不加鎖會導致各種異常情況的發生。所以不設定資料庫隔離級別,資料的變化我們是不能祈求資料庫中資料按照我們預期去改變的。

現在我們知道資料庫 隔離級別 的必要性,接下來討論不同隔離級別會帶來的問題。

3.2 不同隔離級別帶來的問題(重要!含實操部分,最好可以實踐下)

1.前置條件--幾個概念的理解(重要)

不同隔離級別帶來的資料操作問題:
 1.髒讀:兩個事務,t1事務可以讀取到t2事務正在做更改的資料的中間狀態(t2事務執行過程中),而這個資料的更改有可能不會被持久化(commit),而是rollback,導致t1在同一事務內的兩次讀取同一行資料得到結果不同。
 2.不可重複讀:t1事務在整個事務執行過程中讀取某一條記錄多次,發現讀取的此條記錄不是每次都一樣。
 3.幻讀:t1事務在整個事務執行過程中讀取某一範圍內的資料,在第二次讀取時發現多了幾行或者少了幾行。
-----------------------------------------------------------------------------------------------
資料庫中的幾種隔離級別
read uncommited--讀未提交
    該隔離級別指即使一個事務的更新語句沒有提交,但是別的事務可以讀到這個改變,幾種異常情況都可能出現。極易出錯,沒有安全性可言,基本不會使用。
read committed --讀已提交
    該隔離級別指一個事務只能看到其他事務的已經提交的更新,看不到未提交的更新,消除了髒讀和第一類丟失更新,這是大多數資料庫的預設隔離級別,如Oracle,Sqlserver。
repeatable read --可重複讀
    該隔離級別指一個事務中進行兩次或多次同樣的對於資料內容的查詢,得到的結果是一樣的,但不保證對於資料條數的查詢是一樣的,只要存在讀改行資料就禁止寫,消除了不可重複讀和第二類更新丟失,這是Mysql資料庫的預設隔離級別。
serializable --序列化讀
     意思是說這個事務執行的時候不允許別的事務併發寫操作的執行.完全序列化的讀,只要存在讀就禁止寫,但可以同時讀,消除了幻讀。這是事務隔離的最高階別,雖然最安全最省心,但是效率太低,一般不會用。
------------------------------------------------------------------------------------------------
資料庫中的鎖:
1.共享鎖(Share locks簡記為S鎖):也稱讀鎖,事務A對物件T加s鎖,其他事務也只能對T加S,多個事務可以同時讀,但不能有寫操作,直到A釋放S鎖。

2.排它鎖(Exclusivelocks簡記為X鎖):也稱寫鎖,事務A對物件T加X鎖以後,其他事務不能對T加任何鎖,只有事務A可以讀寫物件T直到A釋放X鎖。

3.更新鎖(簡記為U鎖):用來預定要對此物件施加X鎖,它允許其他事務讀,但不允許再施加U鎖或X鎖;當被讀取的物件將要被更新時,則升級為X鎖,主要是用來防止死鎖的。因為使用共享鎖時,修改資料的操作分為兩步,首先獲得一個共享鎖,讀取資料,然後將共享鎖升級為排它鎖,然後再執行修改操作。這樣如果同時有兩個或多個事務同時對一個物件申請了共享鎖,在修改資料的時候,這些事務都要將共享鎖升級為排它鎖。這些事務都不會釋放共享鎖而是一直等待對方釋放,這樣就造成了死鎖。如果一個資料在修改前直接申請更新鎖,在資料修改的時候再升級為排它鎖,就可以避免死鎖。

接下來化繁為簡,配合實操,來看看每種隔離級別場景。不要覺得繁瑣,一定要讀下去。

演示場景配置:
資料庫:mysql 5.7
命令列工具:iterm2.0

1.read uncommited--讀未提交
前置條件:
1.開啟兩個 mysql 客戶端終端
圖片描述
2.檢視當前客戶端事務隔離級別

 命令為:select @@session.tx_isolation;

圖片描述

3.選擇資料庫,建立演示表test,並設定當前客戶端事務隔離級別為read uncommitted.

1.mysql> show databases;
2.mysql> use 你的演示資料庫
3.mysql> CREATE TABLE `test` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
4.insert into test values(1,'張三'),(2,'李四'),(3,'王五');  
4.select @@session.tx_isolation;
5.set session transaction isolation level read uncommitted;
6.select @@session.tx_isolation;

注意:兩個客戶端都需執行set session transaction isolation level read uncommitted;

4.客戶端1,客戶端2設定事務提交模式為 set autocommit = 0;表示關閉預設的自動提交事務功能。

命令:set autocommit = 0;

5.開啟事務

begin;

6.客戶端1 執行 如下指令碼

select name from test where id = 1 ;

結果如下圖所示:

圖片描述

7.客戶端2 執行如下指令碼

update test set name = '張八' where id = 1;

結果如下圖所示:
圖片描述
8.切換到客戶端1執行如下指令碼

select name  from test where id = 1;

結果如下圖所示:

圖片描述
我們發現此時客戶端1再次讀id = 1的記錄時,name 已經從 ‘張三’ 更改為 ‘張八’。

我們繼續執行下一步操作
9.客戶端2執行回滾操作,指令碼如下所示

rollback;

結果如下所示:
圖片描述

10.客戶端1繼續檢視id = 1的記錄,如下指令碼

select name from test where id = 1; 

結果如下所示:

圖片描述

我們發現在客戶端1的一次事務中id = 1 的記錄的name 發生了變化,這種變化就稱之為髒讀
下面我們分析下 read uncommitted 情況下的加鎖情況。
吐槽一句,現在網上的部落格對這個隔離級別的加鎖分析五花八門。
分為三大門派:

1.美團部落格說不加鎖,連結在這: 
2.還有說讀不加鎖(這個我認同),寫加行級共享鎖。連結在這:
[]()
3.還有說讀不加鎖,寫加行級排他鎖(這個我也認同,我做過實踐,稍後會演示),但是說寫完立馬釋放行級排他鎖。

那麼到底是什麼樣子呢,我們看一下
演示過程,開啟3個命令列終端,其中兩個做演示,最後一個客戶端查詢當前 innodb 鎖狀態 設定事務隔離級別為read uncommitted。
做如下演示:
1.客戶端1做如下操作:

update test set name = 'fxliutao' where id = 32;

圖片描述

2.客戶端2做與客戶端相同操作,如下所示

update test set name = 'fxliutao' where id = 32;

我們發現update 操作並沒有執行,而是靜止了
如下圖所示我們分析了在客戶端2鎖等待情況下的加鎖情況:
命令為:

select * from information_schema.INNODB_LOCKSG;

圖片描述

可以得出結論,read uncommitted 隔離級別下,寫操作是有鎖的,而且是 X 排他鎖,可以滅掉上述兩個門派。

並且我們看下上述客戶端2情景下的事務狀態
如下圖所示:

圖片描述

trx_id 為208579的代表的就是客戶端2的事務,trx_state代表的是鎖狀態,代表 客戶端2的事務 處於鎖等待狀態,為什麼是鎖等待狀態呢,因為 客戶端2的事務在更改 id = 32 的記錄時在主鍵上新增了 X(行級排他鎖) 鎖,你可能會有疑問,客戶端1 的更新動作不是已經完成了麼,那麼 客戶端1 肯定已經釋放了在主鍵 id = 32 上的排他鎖了呀,要不為什麼客戶端2 能讀到客戶端1 更改 id = 32 記錄後的髒資料呢?
但是真正的真相是客戶端1在更新完後並沒有釋放排他鎖,因為如果釋放成功,那麼客戶端2的事務是能將 id = 32 的記錄更新成功的,但是並沒有。那既然客戶端1在更新完後並沒有釋放排他鎖,那客戶端2為什麼還能讀到髒資料呢,這跟排他鎖的屬性是相悖的呀(排他鎖會阻塞除當前操作外的其他事務的所有讀寫操作)。
這就是最矛盾的問題,我再SqlServer的官網上找到這句話,事實上也正是這句話讓我茅塞頓開,如下:

Transactions running at the READ UNCOMMITTED level do not issue shared locks to prevent other transactions from modifying data read by the current transaction. READ UNCOMMITTED transactions are also not blocked by exclusive locks that would prevent the current transaction from reading rows that have been modified but not committed by other transactions. When this option is set, it is possible to read uncommitted modifications, which are called dirty reads. Values in the data can be changed and rows can appear or disappear in the data set before the end of the transaction. This option has the same effect as setting NOLOCK on all tables in all SELECT statements in a transaction. This is the least restrictive of the isolation levels.

翻譯是:

在READ UNCOMMITTED級別執行的事務不會發出共享鎖,以防止其他事務修改當前事務讀取的資料。讀取UNCOMMITTED事務也不被排他鎖阻止,這將阻止當前事務讀取已被修改但未被其他事務提交的行。設定此選項時,可以讀取未提交的修改,稱為髒讀。可以更改資料中的值,並且行可以在事務結束之前在資料集中顯示或消失。此選項與在事務中的所有SELECT語句中的所有表上設定NOLOCK具有相同的效果。這是隔離級別的最小限制。

看到了吧讀取UNCOMMITTED事務也不被排他鎖(排他鎖將阻止當前事務讀取已被修改但未被其他事務提交的行)阻止
其實想想也對,應為排它鎖對任何其他的事務開始之前申請的排它鎖,共享鎖都不相容。但是如果我讀不申請鎖,就不會產生上述問題了呀。

所以最終結論是:read uncommitted 讀不加鎖,寫加排他鎖,併到事務結束之後釋放。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4328/viewspace-2798579/,如需轉載,請註明出處,否則將追究法律責任。

相關文章