天天看點

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

目錄

1、什麼是事務?事務舉例?

2、事務的四大特性是什麼?

3、建立事務的SQL文法是什麼?SQL語句實操?

3.1 隐式事務

3.2 顯式事務1

3.3 顯式事務2

4、多個事務在不同僚務隔離級别下的并發問題?

4.1 髒讀

4.2 不可重複讀

4.3 幻讀

4.4 事務操作可能出現的更新丢失問題

4.4.1 第一類更新丢失:復原丢失

4.4.2 第二類更新丢失:覆寫丢失

4.4.3 第二類更新丢失的實際案例和解決如下:

解決方案 1(悲觀鎖):

解決方案 2(樂觀鎖):

結論:

5、不同隔離級别所解決的事務并發問題?

5.1 RU隔離級别

5.2 RC隔離級别下:隻能解決髒讀問題

5.3 RR隔離級别下:隻能解決髒讀/不可重複讀問題

5.4 S隔離級别下:可以解決髒讀/不可重複讀/幻讀問題

99、參考

1、什麼是事務?事務舉例?

(1)事務的定義:事務,是資料庫操作中一組不可再分割的邏輯執行單元,也是資料庫并發控制的基本機關。這個邏輯執行單元中包含了一個或者一組SQL語句,它們是作為一個整體一起向系統送出的,它們要麼全部都執行,要麼全部都不執行。而且,事務執行的結果,必須使資料庫從一個一緻性狀态變到另外一個一緻性狀态。

(2)關于事務,最經典的案例就是銀行轉賬了。比如:小明要給小紅轉賬100元。這個轉賬過程涉及到兩個關鍵操作:操作1是 将小明的賬戶餘額減少100元,操作2是 将小紅的賬戶餘額增加100元。萬一在這兩個操作之間突然出現了錯誤 比如銀行系統暫時性崩潰,導緻小明賬戶餘額減少了 但是小紅賬戶餘額并沒有增加,這樣就不對了。是以,資料庫事務就是要保證這兩個關鍵操作 要麼都執行成功,要麼都執行失敗,而不會出現 一部分SQL語句執行成功 一部分SQL語句執行失敗的情況。

2、事務的四大特性是什麼?

在關系型資料庫中,一個事務往往需要遵循四個特性,也就是我們平常說的ACID四大基本原則。介紹如下:

(1)A,atomicity,原子性:事務是一個最小的邏輯執行單元,不允許再被分割。事務的原子性就是要確定 這一組操作要麼全部都執行完成,要麼全部都不執行。

(2)C,consistency,一緻性:一個事務的執行,應該把系統從一個一緻性狀态變到另一個一緻性狀态。這裡的一緻性狀态是指,這個事務要保護定義在資料上的所有完整性限制。比如,銀行轉賬的案例。轉賬之前,小明賬戶餘額是1000元,小紅賬戶餘額是1000元,餘額之和是2000元。轉賬100元之後,小明賬戶餘額是900元,小紅賬戶餘額是1100元,餘額之和仍然是2000元。這就是一緻性狀态。

(3)I,isolation,隔離性:當有多個使用者并發通路資料庫時,一個使用者的事務不能被其他所有的事務所幹擾,各個并發事務之間是互相獨立的,互不幹擾的。 

(4)D,durability,持久性:一個事務被送出之後,它對資料庫中資料的改變是持久的,即使資料庫發生了故障也不應該丢失這個改變。

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

3、建立事務的SQL文法是什麼?SQL語句實操?

3.1 隐式事務

隐式事務:事務沒有明顯的開啟或者結束的标志,在MySQL中,預設是開啟自動送出的 ( autocommit = ON )。另外:開啟事務的自動送出:set autocommit = 1;  關閉事務的自動送出:set autocommit = 0; 

是以,針對 select、update、delete、insert 等DQL語句以及DML語句的執行,MySQL都會自動開啟一個事務并且送出該事務。如果關閉了自動送出事務,則需要手動送出或者手動復原來完成事務的結束操作。

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

3.2 顯式事務1

顯式事務:與隐式事務相反,有明顯的開啟事務或結束事務的标志。

set autocommit=0;
start transaction;                    #可選的語句
select | update | delete | insert;    #DQL操作或者DML操作
commit | rollback;                    #送出事務,或者復原事務
set autocommit=1;                     #案例示範結束後,把屬性值再改回到原來的值
           
【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

從上圖中可以看出:在事務未結束之前,事務中的操作是有效的而且的确更改了資料實體。那麼試想一下:如果這是有多個事務參與, 肯定會出現各種各樣的資料不一緻的情況,這就類似于多個線程在沒有鎖的情況下修改同一個全局變量。

3.3 顯式事務2

顯式事務:復原點的使用(setpoint)。

set autocommit=0;
start transaction;
[select | update | delete | insert];  #復原時,也要執行送出的部分
savepoint p;                          #設定復原點,且變量名為p
[DELETE | UPDATE | INSERT | SELECT];  #復原時,不需要執行送出的部分
rollback to p;                        #復原時與rollback to搭配使用
set autocommit=1;                     #案例示範結束後,把屬性值再改回到原來的值
           
【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

可以發現:在復原點p之前的資料實體被删除了、復原點p之後的資料實體因為復原而被撤銷了操作。

注意:在事務中使用truncate時,就算rollback也會清空整張表。

4、多個事務在不同僚務隔離級别下的并發問題?

當多個用戶端并發通路同一個資料表的時候,有可能會出現資料的一緻性問題。常見的資料一緻性問題就是:髒讀、不可重複度、幻讀。

在MySQL下,事務的隔離級别共有四種,且由低到高依次為:讀未送出 RU ( Read Uncommitted ) 、讀已送出 RC ( Read Committed ) 、可重複讀 RR ( Repeatable Read (MySQL預設的隔離級别) )、串行化 S ( Serializable ) 。這四個隔離級别中的後面三個 可以逐個解決 髒讀 、不可重複讀 、幻讀 這三類問題。

指令小計:

show variables like 'transaction_isolation';                          #查詢隔離級别

set session transaction isolation level read uncommitted;   #更改隔離級别

set session transaction isolation level read committed;

set session transaction isolation level repeatable read;

set session transaction isolation level serializable;

4.1 髒讀

(1)定義:在一個事務中讀取了另外一個事務還沒有送出的資料,如果另外一個事務做了資料復原操作,那麼第一個事務讀取的資料就是髒資料,這會導緻業務出錯。這個現象就是髒讀。    簡單解釋:對于兩個事務T1和T2,T1讀取了 已經被T2更新但是還沒有被送出的資料,如果此時 T2做了復原操作,那麼T1讀取的内容就是髒資料。

(2)示範:

(2.1)開啟兩個mysql用戶端,并建立一張測試表transaction。

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

(2.2)更改MySQL的隔離級别,從預設隔離級别 REPEATABLE READ 修改為 READ UNCOMMITTED 。

show variables like 'transaction_isolation';                 #查詢隔離級别
set session transaction isolation level read uncommitted;    #更改隔離級别為:RU
           
【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

(2.3)開啟兩個用戶端界面,兩個用戶端同時開啟事務,其中一個事務做update操作,另一個事務做select操作。

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

若此時黑色背景的用戶端進行復原,則白色背景的用戶端讀取的資料就是髒資料,即髒讀。

4.2 不可重複讀

(1)定義:在一個事務的前後兩次查詢中,前後兩次查詢出來的資料不一緻,這可能是因為在前後兩次查詢的過程中 插入了另外一個事務送出的内容,這個送出的内容可以是:更新了原有的資料,或者是删除了原有的資料。這個現象就是不可重複讀。    簡單解釋:對于兩個事務T1和T2,T1讀取了一個字段,然後在T2中對該字段做了更新操作并且送出了,T1再次讀取該同一個字段時,發現該字段的值和之前的不一樣了。

(2)示範:

show variables like 'transaction_isolation';                          #查詢隔離級别

set session transaction isolation level read committed;      #更改隔離級别為:RC

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

對于白色背景用戶端,在事務中的第一次查詢和第二次查詢的過程中 插入了黑色背景用戶端送出的事務,導緻白色背景用戶端中的事務在前後兩次重複讀取時的結果不一緻,這就是不可重複讀。

4.3 幻讀

(1)定義:在一個事務的前後兩次查詢中資料的筆數不一緻。例如:事務1查詢了幾行資料,而另外一個事務2卻在此時插入了新的幾行資料而且送出了,那麼先前的事務1在接下來的查詢中 就會發現有幾行資料是新的,是之前那次查詢中所沒有的,這個現象就是幻讀。    簡單解釋:對于兩個事務T1和T2,T1從表中讀取資料,然後T2進行了insert操作并送出了,當T1再次讀取的時候,發現前後兩次讀取的結果不一緻,有新增的資料出現。

(2)示範:

show variables like 'transaction_isolation';                          #查詢隔離級别

set session transaction isolation level repeatable read;      #更改隔離級别為:RR

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

4.4 事務操作可能出現的更新丢失問題

更新丢失問題有兩類:第一類更新丢失和第二類更新丢失。

(1)第一類更新丢失(復原丢失):事務A 的復原 覆寫了 事務B 已送出的結果。

(2)第二類更新丢失(覆寫丢失):事務A 的送出 覆寫了 事務B 已送出的結果。

說白了,更新丢失問題描述的就是兩個事務之間的【寫-寫沖突】。

第一類更新丢失,就是一個事務的後復原  覆寫了  另一個事務的前面的送出。

第二類更新丢失,就是一個事務的後送出  覆寫了  另一個事務的前面的送出。

經過測試,MySQL 5.7 版本下,所有的隔離級别都沒有出現更新丢失問題,即 MySQL 5.7 版本下的隔離級别預設都是可以解決更新丢失問題的,是以,更新丢失的情況并不好模拟。了解一下即可,我們這裡不詳細讨論。【第一類更新丢失和第二類更新丢失的闡述】

4.4.1 第一類更新丢失:復原丢失

事務A 復原時,把事務B已經送出的更新資料給覆寫了。這種錯誤可能造成很嚴重的問題,通過下面的賬戶取款轉賬就可以看出來:

事務A 在復原時,“不小心” 将事務B 已經轉入賬戶的金額給抹去了。SQL92 沒有定義這種現象,在資料庫中,标準定義的4種隔離界别都不允許第一類更新丢失的發生。

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

4.4.2 第二類更新丢失:覆寫丢失

事務A 覆寫了 事務B 已經送出的更新資料,造成事務B 所做的操作丢失。

下面的例子裡,由于支票轉賬事務A 覆寫了取款事務B 對存款餘額所做的更新,導緻銀行最後損失了100元。相反,如果轉賬事務A 先送出、取款事務B 後送出,那麼使用者賬戶将損失100元。

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

4.4.3 第二類更新丢失的實際案例和解決如下:

第二類更新丢失,實際上和不可重複讀是同一種問題。有些系統中,第二類更新丢失可能就影響很大了,舉個簡單的例子:

财務系統加工資,若公司本次調薪決定給員工張三加1k人民币,财務部兩名操作人員A和B,過程情況若是這樣的:

1)A操作員在應用系統的頁面上查詢出張三的薪水資訊,然後選擇薪水記錄進行修改,打開修改頁面後,但A突然有事離開了,頁面放在那沒有做任何的送出。

2)這時候,B操作員同樣在應用中查詢出張三的薪水資訊,然後選擇薪水記錄進行修改,錄入增加薪水額1000,然後送出了。

3)這時候,A操作員回來了,在自己之前打開的薪水修改頁面上也錄入了增加薪水額1000,然後送出了。

其實上面例子操作員A和B隻要是一前一後做送出,悲劇就出來了。背景修改薪水的sql:update 工資表 set salary = salary + 增加薪水額 where staff_id = ‘員工ID’。

這個過程走下來後結果是:張三開心死了,這次漲了2k,操作員A和B都郁悶了。

第二類更新丢失問題的解決思路,基本上是兩種思路,一種是悲觀鎖,另外一種是樂觀鎖。

簡單的說就是:一種假定這樣的問題是高機率的,最好一開始就鎖住資料,免得更新老是失敗。另外一種假定這樣的問題是小機率的,最後一步做更新送出的時候再鎖住資料,免得資料的鎖住時間太長以影響其他人做有關操作。

解決方案 1(悲觀鎖):

A、傳統的悲觀鎖法(不推薦):

以上面的例子來說明,在彈出修改工資的頁面初始化時(這種情況下一般會從資料庫中查詢出來),在這個初始化查詢中使用 select ...for update, 通過添加 for update 語句,将這條記錄鎖住,避免其他使用者更新,進而保證後續的更新都是在正确的狀态下更新的。然後,在保持這個連結的狀态下,再做更新送出。當然這有個前提就是要保持連結,就是要對連結占用較長時間,這個在現在的web系統高并發下顯然是不現實的。

B、現在的悲觀鎖法(推薦優先使用):

在修改工資這個頁面做送出時先查詢下,當然這個查詢必須也要加鎖( select ...for update ),有人會說,在這裡做個查詢确認記錄是否有改變不就行了嗎?是的,是要做個确認,隻是你不加 for update 就不能保證你【從查詢出來 到 更新送出這段時間裡】這條記錄有沒有被其他會話更新過,是以,這種方式也需要在查詢時鎖定記錄,保證在這條記錄沒有變化的基礎上再做更新,若有變化則提示告知使用者。

解決方案 2(樂觀鎖):

A、舊值條件(前鏡像)法:(不推薦):

就是在SQL更新時使用舊的狀态值做條件,SQL大緻如下 Update table set col1 = newcol1value, col2 = newcol2value …. where col1 = oldcol1value and col2 = oldcol2value …. ,在上面的例子中,我們就可以把目前工資作為條件進行更新,如果這條記錄已經被其他會話更新過,則本次更新了0行,這裡我們應用系統一般會做個提示告知使用者重新查詢更新。這個取哪些舊值作為條件更新視具體系統實際情況而定。(這種方式可能會發生阻塞,如果應用其他地方使用悲觀鎖長時間鎖定了這條記錄,則本次會話就需要等待,是以,使用這種方式時最好統一使用樂觀鎖法。)

B、使用版本列法(推薦優先使用):

其實這種方式是一個特殊化的前鏡像法,就是不需要使用多個舊值做條件,隻需要在表上加一個版本列,這一列可以是 version 或 timestamp 列,加這列的作用就是用來記錄這條資料的版本(在表設計時一般我們都會給每個表增加一些number型和date型的備援字段,以便擴充使用,這些備援字段完全可以作為版本列使用),在應用程式中我們每次操作時都同時對版本列做維護即可。在更新時我們把上次版本作為條件進行更新。

結論:

綜上所述,我們對【更新丢失問題】建議采取上面的【悲觀鎖B方法】或【樂觀鎖B方法】,紅字加粗體已标注,其實這兩種方式的本質都一樣,都是在更新送出時做一次查詢确認再更新送出,我個人覺得都是樂觀的做法,差別在于悲觀鎖B方法是通過 select .. for update 這種顯示加鎖的方式,這個可能會導緻其他會話的阻塞,而樂觀鎖B方法需要多元護一個版本列。個人建議:在使用者并發數比較少且沖突比較嚴重的應用系統中選擇悲觀鎖B方法,其他情況首先選擇樂觀鎖版本列法。

5、不同隔離級别所解決的事務并發問題?

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

5.1 RU隔離級别

READ UNCOMMITTED:RU隔離級别不做示範,其隔離性最低,會出現髒讀、不可重複讀、幻讀等所有情況。

5.2 RC隔離級别下:隻能解決髒讀問題

(1)可以避免髒讀(髒讀:一個事務讀取到另外一個事務還未送出的資料)。

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

(2)無法避免不可重複讀問題(不可重複讀:一個事務讀取到另外一個事務已經送出的資料,另外一個事務是update或delete操作)。

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

5.3 RR隔離級别下:隻能解決髒讀/不可重複讀問題

(1)可以避免不可重複讀問題(不可重複讀:一個事務讀取到另外一個事務已經送出的資料,另外一個事務是update或delete操作)。

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

(2)無法避免幻讀問題(幻讀:一個事務前後多次查詢整表資料,由于其他事務新增記錄造成多次查詢的記錄條數不同)。

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

5.4 S隔離級别下:可以解決髒讀/不可重複讀/幻讀問題

(1)SERIALIZABLE 避免幻讀情況,阻塞方式。

【MySQL事務】2-MySQL中事務的全面與深入了解1、什麼是事務?事務舉例?2、事務的四大特性是什麼?3、建立事務的SQL文法是什麼?SQL語句實操?4、多個事務在不同僚務隔離級别下的并發問題?5、不同隔離級别所解決的事務并發問題?99、參考

可以看出,serializable 級别就類似加鎖的方式,同一時刻支援多個事務并發,但是針對DML(UPDATE\INSERT\DELETE)操作時,目前發起操作的事務會被阻塞,直到其他事務commit或者rollback才會繼續執行該事務語句。可見效率十分低下,一般情況不使用。

99、參考

(1)https://blog.csdn.net/cmm0401/article/details/107196403

(2)https://blog.csdn.net/cmm0401/article/details/107168147

(3)https://blog.csdn.net/cmm0401/article/details/107013646

(4)https://www.cnblogs.com/kisun168/p/11320549.html

(5)https://www.jianshu.com/p/7e76ce65e3ad

(6)https://blog.csdn.net/qq_36581961/article/details/113106530

繼續閱讀