面試官:談?wù)凪ySQL如何保證事務(wù)并發(fā)安全?我:......

前言

前一段時間我在找工作的時候,經(jīng)常會被問到事務(wù)以及事務(wù)的隔離級別等問題,正好最近在公司對事務(wù)隔離級別的原理做了一次技術(shù)分享,今天把它整理出來分享給大家。如果大家覺得有不對的地方呢,可以在評論區(qū)指出。

事務(wù)

一、什么是事務(wù)

事務(wù),即一組數(shù)據(jù)庫操作的集合,這組操作要么全部執(zhí)行成功,任意一個操作失敗那么所有操作全部回滾。

二、事務(wù)的特性

事務(wù)有四大特性,即原子性,一致性,隔離性,持久性。這四個特性通常稱為ACID特性。

  1. 原子性(Atomicity):一個事務(wù)是一個不可分割的單位。一個事務(wù)中的操作要么全部執(zhí)行,要么全不執(zhí)行。

    例如一個賬戶A向另一個賬戶B轉(zhuǎn)賬,A賬戶的余額減少,B賬戶的余額就要增加,兩個操作一定同時成功或者同時失敗。

  2. 一致性(Consistency):事務(wù)使數(shù)據(jù)由一個狀態(tài)變?yōu)榱硪粋€狀態(tài),數(shù)據(jù)的完整性保持穩(wěn)定。

    例如轉(zhuǎn)賬操作中,A賬戶轉(zhuǎn)賬給B賬戶,A賬戶減少的金額與B賬戶增加的金額必須是相同的。

  3. 隔離性(Isolation):一個事務(wù)的執(zhí)行不能被其他事務(wù)干擾。即一個事務(wù)的內(nèi)部操作及使用的數(shù)據(jù)對并發(fā)的其他事務(wù)是隔離的,并發(fā)執(zhí)行的各個事務(wù)之間不能互相干擾。

    事務(wù)互相干擾就會造成數(shù)據(jù)不一致,隔離性的最終目的是為了保證一致性。

  4. 持久性(Durability):一個事務(wù)一旦提交,它對數(shù)據(jù)庫中的數(shù)據(jù)的改變應(yīng)是永久的。

    只要事務(wù)提交成功,對數(shù)據(jù)庫的修改就保存下來了,不會因為任何原因再回到修改前的狀態(tài)。

三、事務(wù)的狀態(tài)

事務(wù)共有五種狀態(tài):

  1. 活動狀態(tài)
    事務(wù)在執(zhí)行時所處的狀態(tài)稱為活動狀態(tài)。
  2. 部分提交狀態(tài)
    事務(wù)中的最后一個操作被執(zhí)行后的狀態(tài)叫做部分提交狀態(tài)。此時變更還未刷新到磁盤上。
  3. 失敗狀態(tài)
    事務(wù)不能正常執(zhí)行的狀態(tài)叫做失敗狀態(tài)。
  4. 提交狀態(tài)
    事務(wù)在經(jīng)過部分提交狀態(tài)后,將所有的變更數(shù)據(jù)寫入磁盤,寫完后進(jìn)入提交狀態(tài),標(biāo)志著事務(wù)成功執(zhí)行完成。
  5. 中止?fàn)顟B(tài)
    事務(wù)執(zhí)行失敗,數(shù)據(jù)回滾至事務(wù)執(zhí)行前,此時事務(wù)所處的狀態(tài)叫做中止?fàn)顟B(tài)。

四、事務(wù)的隔離級別

事務(wù)具有隔離性,隔離性的最終目的是為了保證數(shù)據(jù)一致性。而實現(xiàn)事務(wù)隔離性最簡單的辦法就是讓事務(wù)串行執(zhí)行,類似線程的串行執(zhí)行,但這種方式效率低下,性能較差。所以在保證隔離性的前提下,為了不犧牲性能,事務(wù)能夠支持多種隔離級別。
我們先來看一下當(dāng)多個事務(wù)同時處理同一批數(shù)據(jù)時,如果沒有采取有效的隔離機制,會發(fā)生哪些問題。

  1. 丟失修改
    事務(wù)1和事務(wù)2對同一數(shù)據(jù)進(jìn)行修改,其中事務(wù)2提交的數(shù)據(jù)結(jié)果破壞了事務(wù)1提交的結(jié)果,導(dǎo)致事務(wù)1的修改失效。例如火車售票系統(tǒng),售票點1(事務(wù)1)查詢車票余票有200張,售票點2(事務(wù)2)查詢車片也有200張;售票點1賣出1張車票,剩余車票199張,寫回數(shù)據(jù)庫;售票點2同樣賣出1張車票,剩余車票199張,寫回數(shù)據(jù)庫;此時共賣出2張車票,應(yīng)剩余198張車票,但庫存仍有199張,售票點1的修改被破壞了。
  2. 臟讀
    事務(wù)1修改某一數(shù)據(jù)時,事務(wù)2讀取該數(shù)據(jù),此時事務(wù)1因某些原因需要撤銷修改,將數(shù)據(jù)回滾,恢復(fù)為修改前,而事務(wù)2讀取的是修改后的數(shù)據(jù),與此時數(shù)據(jù)庫中的該值不一致,事務(wù)2讀取到的數(shù)據(jù)即為臟讀。
  3. 幻讀
    事務(wù)1進(jìn)行兩次讀操作,第一次讀取了n條數(shù)據(jù),此時事務(wù)2對這n條數(shù)據(jù)進(jìn)行了刪除或插入m條數(shù)據(jù),事務(wù)1進(jìn)行第二次讀操作,此時讀到的數(shù)據(jù)為n-m或n+m條,仿佛出現(xiàn)了幻覺,即為幻讀。
  4. 不可重復(fù)讀
    事務(wù)1進(jìn)行兩次讀操作,第一次讀取了某一數(shù)據(jù),此時事務(wù)2對這條數(shù)據(jù)進(jìn)行了修改,事務(wù)1進(jìn)行第二次讀操作,此時讀到的數(shù)據(jù)與第一次讀到數(shù)據(jù)結(jié)果不一致,即為不可重復(fù)讀。

注:幻讀與不可重復(fù)讀有些相似,但幻讀強調(diào)的是數(shù)據(jù)記錄的增加或刪減,不可重復(fù)讀強調(diào)的是數(shù)據(jù)記錄的修改。

產(chǎn)生以上四類情況的主要原因是并發(fā)操作破壞了事務(wù)的隔離性,所以要對事務(wù)進(jìn)行并發(fā)控制,使得一個事務(wù)的執(zhí)行不受其他事務(wù)的干擾,避免造成數(shù)據(jù)不一致。根據(jù)以上造成數(shù)據(jù)不一致的情況不同,數(shù)據(jù)庫也具有不同的處理方式,即事務(wù)隔離級別。事務(wù)的隔離級別越高,能解決的數(shù)據(jù)不一致問題越多,但性能損耗也越大。四種事務(wù)的隔離級別如下。

  1. 讀未提交(Read uncommitted)
    一個事務(wù)可以讀取另一個事務(wù)未提交的修改。是最低的隔離級別。
  2. 讀已提交(Read committed)
    一個事務(wù)只能讀取另一個事務(wù)已提交的修改。該級別可以解決臟讀問題。
  3. 可重復(fù)讀(Repeatable read)
    事務(wù)開始讀取數(shù)據(jù)時不允許其他事務(wù)對這些數(shù)據(jù)進(jìn)行修改。保證了同一事務(wù)多次讀取同樣的記錄結(jié)果一致。該級別可以解決不可重復(fù)讀的問題,但不能完全解決幻讀問題。
  4. 可串行化(Serializable)
    最該級別的事務(wù)隔離級別。強制事務(wù)串行執(zhí)行,可以解決臟讀、不可重復(fù)讀、幻讀問題,但效率低下,通常不使用這種方式。

注:可重復(fù)讀是InnoDB的默認(rèn)事務(wù)隔離級別,且已能夠達(dá)到了SQL標(biāo)準(zhǔn)的可串行化。

MVCC與鎖

一、什么是MVCC

MVCC(Multi Version Concurrency Control),即多版本并發(fā)控制。通過維護(hù)數(shù)據(jù)的歷史版本,解決并發(fā)訪問下的數(shù)據(jù)不一致性。

二、MVCC的原理

1.undo log
undo log是InnoDB的事務(wù)日志。undo log是回滾日志,記錄的是行數(shù)據(jù)的修改記錄,即哪些行被修改成怎樣,提供回滾操作。事務(wù)的操作記錄會被記錄到undo log中,用于事務(wù)進(jìn)行回滾操作。
2.版本鏈
在InnoDB中,每個行記錄都隱藏著兩個字段:
??1)trx_id:事務(wù)id。該字段用于記錄修改當(dāng)前行記錄的事務(wù)的id。
??2)roll_pointer:回滾指針。該字段用于記錄修改當(dāng)前行記錄的undo log地址。
假設(shè)一張學(xué)生分?jǐn)?shù)表t_student_score有id,name,class,score四個字段,此時只有一條記錄,當(dāng)前執(zhí)行插入操作的事務(wù)id為1,則有如下示例圖:


假設(shè)此時有兩個事務(wù)t1、t2,id分別為2、3的事務(wù)對這條記錄進(jìn)行修改,執(zhí)行如下操作:

由于每次數(shù)據(jù)的修改都會在undo log中產(chǎn)生日志記錄下來,且roll_pointer會指向undo log的地址。所以,兩次修改后的日志通過roll_pointer串聯(lián)起來,形成的版本鏈如下圖所示:

如圖示,版本鏈的頭結(jié)點是最新的行記錄,而歷史行記錄由roll_pointer記錄的undo log地址串聯(lián)起來。如果數(shù)據(jù)庫隔離級別為讀未提交,那么讀取版本鏈中最新的數(shù)據(jù)即可;如果數(shù)據(jù)庫隔離級別為可串行化,事務(wù)之間是串行執(zhí)行的,不會發(fā)生數(shù)據(jù)不一致的情況,直接執(zhí)行讀操作即可;如果數(shù)據(jù)庫隔離級別為讀已提交或可重復(fù)讀,那么就需要遍歷整條版本鏈,找到trx_id與當(dāng)前事務(wù)相同的記錄,即需要判斷版本鏈中哪個版本是當(dāng)前事務(wù)可見的。InnoDB通過ReadView實現(xiàn)了這個功能。
ReadView主要包括四個部分:

  1. m_ids:表示在生成ReadView時當(dāng)前系統(tǒng)中活躍的讀寫事務(wù)的id。
  2. min_trx_id:表示在生成ReadView時當(dāng)前系統(tǒng)中活躍的讀寫事務(wù)的最小id。
  3. max_trx_id:表示在生成ReadView時系統(tǒng)應(yīng)該分配給下一個事務(wù)的id值。
  4. creator_trx_id:表示生成該ReadView的事務(wù)的id。

有了這個ReadView,這樣在訪問某條記錄時,只需要按照下邊的步驟判斷記錄的某個版本是否可見:

  1. 如果被訪問版本的trx_id的值與ReadView中的creator_trx_id值相同,意味著當(dāng)前事務(wù)在訪問它自己修改過的記錄,所以該版本可以被當(dāng)前事務(wù)訪問。
  2. 如果被訪問版本的trx_id的值小于ReadView中的min_trx_id值,表明生成該版本的事務(wù)在當(dāng)前事務(wù)生成ReadView前已經(jīng)提交,所以該版本可以被當(dāng)前事務(wù)訪問。
  3. 如果被訪問版本的trx_id的值大于或等于ReadView中的max_trx_id值,表明生成該版本的事務(wù)在當(dāng)前事務(wù)生成ReadView后才開啟,所以該版本不可以被當(dāng)前事務(wù)訪問。
  4. 如果被訪問版本的trx_id的值在ReadView的min_trx_id和max_trx_id之間,那就需要判斷一下trx_id屬性值是不是在m_ids列表中,如果在,說明創(chuàng)建ReadView時生成該版本的事務(wù)還是活躍的,該版本不可以被訪問;如果不在,說明創(chuàng)建ReadView時生成該版本的事務(wù)已經(jīng)被提交,該版本可以被訪問。

在MySQL中,讀已提交和可重復(fù)讀兩種隔離級別的一個非常大的區(qū)別就是它們生成ReadView的時機不同。讀已提交在每次讀數(shù)據(jù)前都會生成一個ReadView,這樣可以保證每次都能讀到其他事務(wù)已提交的數(shù)據(jù)。可重復(fù)讀只在第一次讀取數(shù)據(jù)時生成一個ReadView,這樣就能保證后續(xù)讀取的結(jié)果一致。

三、鎖

我們在開發(fā)過程中,常常遇到多個線程同時訪問一個共享資源,往往需要并發(fā)控制來確保執(zhí)行結(jié)果正確,在數(shù)據(jù)庫事務(wù)中同樣如此。事務(wù)也存在并發(fā)訪問,即多個事務(wù)同時訪問數(shù)據(jù)。事務(wù)的并發(fā)訪問一般有三種情況:

1. 讀-讀操作:并發(fā)事務(wù)同時訪問同一行或同一段數(shù)據(jù)記錄。由于事務(wù)都是進(jìn)行讀操作,不會對數(shù)據(jù)造成影響,因此并發(fā)讀操作完全允許。  
2. 寫-寫操作:并發(fā)事務(wù)同時修改同一行或同一段數(shù)據(jù)記錄。由于事務(wù)都是進(jìn)行寫操作,極易發(fā)生丟失修改問題,因此要通過加鎖解決,即當(dāng)一個事務(wù)要對某行修改時,首先會給該行加鎖,如果加鎖成功才可以進(jìn)行修改;如果加鎖失敗,就要排隊等待,待加鎖事務(wù)提交或回滾后將鎖釋放。  
3. 讀-寫操作:一個事務(wù)進(jìn)行讀操作,另一個事務(wù)進(jìn)行寫操作。這種情況容易產(chǎn)生臟讀、幻讀、不可重復(fù)讀問題。最好的解決方案是讀操作進(jìn)行多版本并發(fā)控制(MVCC),寫操作加鎖。  

根據(jù)鎖的作用范圍分類,可以將鎖分為表級鎖和行級鎖。表級鎖作用于數(shù)據(jù)庫表上,粒度較大;行級鎖作用于數(shù)據(jù)行上,粒度較小。
為了實現(xiàn)讀-讀操作不受影響,寫-寫操作、讀-寫操作能夠互相阻塞,MySQL使用了讀寫鎖的思想,實現(xiàn)了共享鎖與排他鎖:

1. 共享鎖(S鎖):用于不更改或不更新數(shù)據(jù)的操作,如SELECT語句。共享鎖可以在同一時刻被多個事務(wù)持有。獲得共享鎖的事務(wù)只能讀取數(shù)據(jù),不能更改數(shù)據(jù)。我們可以通過SELECT ... LOCK IN SHARE MODE手工加共享鎖。需要注意的是如果一個事務(wù)對數(shù)據(jù)加上了共享鎖,其他事務(wù)只能對這部分?jǐn)?shù)據(jù)再加共享鎖,不能加排它鎖。  
2. 排他鎖(X鎖):用于修改數(shù)據(jù)操作,如INSERT、UPDATE、DELETE。確保事務(wù)不會同時對同一部分?jǐn)?shù)據(jù)進(jìn)行多重修改,在同一時刻只能被一個事務(wù)持有。排他鎖的加鎖方式有兩種,第一種是自動加鎖,在對數(shù)據(jù)進(jìn)行增刪改時都會默認(rèn)加上一個排他鎖。另一種是手工加鎖,使用SELECT ... FOR UPDATE可以實現(xiàn)手工加排他鎖。  

考慮一個場景,事務(wù)t1給某行數(shù)據(jù)加行級共享鎖,讓該行數(shù)據(jù)只能讀不能寫,之后事務(wù)t2申請表級排他鎖,讓整張表的數(shù)據(jù)只能寫不能讀。如果事務(wù)t2的鎖申請成功,那么它可以修改表里的任意一行數(shù)據(jù),這與t1持有的行鎖是沖突的。那么數(shù)據(jù)庫要如何判斷這個沖突呢?
首先需要判斷表是否已被其他事務(wù)加了表鎖;然后需要判斷表中每一行是否加了行鎖。這種判斷方式需要遍歷表中的每一行,效率低下。于是有了意向鎖(I鎖)。
意向鎖可以認(rèn)為是S鎖和X鎖在數(shù)據(jù)表上的標(biāo)識,通過意向鎖可以快速判斷表中是否有記錄被上鎖,從而避免通過遍歷的方式來查看表中有沒有記錄被上鎖,提升加鎖效率。意向鎖是由數(shù)據(jù)庫自己維護(hù)的。當(dāng)我們給一行數(shù)據(jù)加上共享鎖之前,數(shù)據(jù)庫會自動先申請表的意向共享鎖(IS鎖);當(dāng)我們給一行數(shù)據(jù)加上排他鎖之前,數(shù)據(jù)庫會自動先申請表的意向排他鎖(IX鎖)。例如,我們要加表級別的X鎖,首先判斷表上是否有被其他事務(wù)加了表鎖,如果沒有,再檢查是否有意向鎖,此時直接根據(jù)意向鎖就能知道這張表是否有行級別的X鎖或者S鎖,這時候數(shù)據(jù)表里面如果存在行級別的X鎖或者S鎖的,加鎖就會失敗。

四、InnoDB中的鎖

1.表級鎖
InnoDB中的表級鎖主要包括表級別的意向共享鎖(IS鎖),意向排他鎖(IX鎖)以及自增鎖(AUTO-INC鎖)。IX鎖和IS鎖已經(jīng)介紹過了,下面介紹一下自增鎖。
大家都知道,如果我們給某列字段加了AUTO_INCREMENT自增屬性,插入的時候不需要為該字段指定值,系統(tǒng)會自動保證遞增。系統(tǒng)實現(xiàn)這種自動給AUTO_INCREMENT修飾的列遞增賦值的原理主要是兩個:
??1)AUTO-INC鎖:在執(zhí)行插入語句的時先加上表級別的AUTO-INC鎖,插入執(zhí)行完成后立即釋放鎖。如果我們的插入語句在執(zhí)行前無法確定具體要插入多少條記錄,比如INSERT ... SELECT這種插入語句,一般采用AUTO-INC鎖的方式。
??2)輕量級鎖:在插入語句生成AUTO_INCREMENT值時先才獲取這個輕量級鎖,然后在AUTO_INCREMENT值生成之后就釋放輕量級鎖。如果我們的插入語句在執(zhí)行前就可以確定具體要插入多少條記錄,那么一般采用輕量級鎖的方式對AUTO_INCREMENT修飾的列進(jìn)行賦值。這種方式可以避免鎖定表,可以提升插入性能。

2.行級鎖
在了解InnoDB的行級鎖之前,我們先簡單了解一下當(dāng)前讀和快照讀。

1)當(dāng)前讀:即加鎖讀。讀取記錄的最新版本,會加鎖保證其他并發(fā)事務(wù)不能修改當(dāng)前記錄,直至獲取鎖的事務(wù)釋放鎖。使用當(dāng)前讀的操作主要包括:顯示加鎖的讀操作與插入、更新、刪除等寫操作。  
2)快照讀:即不加鎖讀。讀取記錄的快照版本而非最新版本,通過MVCC實現(xiàn)。InooDB在可重復(fù)讀隔離級別下,如果不顯示的加LOCK IN SHARE MODE、FOR UPDATE的SELECT操作都屬于快照讀,保證事務(wù)執(zhí)行過程中只有第一次讀之前提交的修改和自己的修改可見,其他的均不可見。  

綜上可知,通過MVCC可以解決臟讀、不可重復(fù)讀、幻讀這些讀一致性問題,但是這只是解決了普通SELECTD的數(shù)據(jù)讀取問題,即快照讀的讀取問題。在當(dāng)前讀,即加鎖讀的情況下依然要解決臟讀、不可重復(fù)讀、幻讀問題。這個時候需要在讀取的記錄上加鎖,由于都是在行記錄上加鎖,這些鎖都稱為行級鎖。
InnoDB的行鎖是通過鎖住索引來實現(xiàn)的,如果加鎖查詢時沒有使用索引,會將整個表的聚簇索引鎖住,相當(dāng)于鎖住整個表。根據(jù)鎖定范圍不同,行鎖可分為:

  1. 記錄鎖(Record Lock):單個行記錄上的鎖。
  2. 間隙鎖(Gap Lock):鎖定一個范圍,但不包括記錄本身。
  3. 臨鍵鎖(Next-Key Lock):是記錄鎖和間隙鎖的結(jié)合。鎖定一個范圍,包括記錄本身。是MySQL的默認(rèn)行鎖。
    間隙鎖和臨鍵鎖都是用來解決幻讀的。

我們來測試一下在什么情況下會產(chǎn)生鎖。

測試準(zhǔn)備

環(huán)境:數(shù)據(jù)庫MySQL,數(shù)據(jù)庫引擎InnoDB,默認(rèn)的事務(wù)隔離級別(RR)。

庫表:

主鍵索引測試表

CREATE TABLE `t_num_test` (
  `id` int NOT NULL AUTO_INCREMENT,
  `num` int DEFAULT NULL
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

數(shù)據(jù)初始化

INSERT INTO `t_num_test` VALUES ('1', '200');
INSERT INTO `t_num_test` VALUES ('5', '300');
INSERT INTO `t_num_test` VALUES ('9', '400');
INSERT INTO `t_num_test` VALUES ('13', '500');

普通索引測試表

CREATE TABLE `t_num_normal_test` (
  `id` int NOT NULL AUTO_INCREMENT,
  `num` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_num` (`num`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

數(shù)據(jù)初始化

INSERT INTO `t_num_normal_test` VALUES ('1', '1');
INSERT INTO `t_num_normal_test` VALUES ('5', '5');
INSERT INTO `t_num_normal_test` VALUES ('9', '9');
INSERT INTO `t_num_normal_test` VALUES ('13', '13');

關(guān)閉事務(wù)自動提交

#查看事務(wù)自動提交是否開啟
SHOW VARIABLES LIKE '%autocommit%'; 
#關(guān)閉事務(wù)自動提交
SET autocommit = 0;

為了測試方便,我們讓兩個數(shù)據(jù)表的id相同,普通索引測試表的普通索引列的值與id列的值相同。

開始測試之前,我們來看一下兩個數(shù)據(jù)表存在的區(qū)間有哪些。

  1. (-∞,1]

  2. (1,5]

  3. (5,9]

  4. (9,13]

  5. (13,+∞)

因為兩個表的數(shù)據(jù)值基本一致,所以對于主鍵索引測試表來說,上述區(qū)間根據(jù)t_num_test.id列的值生成;對于普通索引測試表來說,上述區(qū)間根據(jù)t_num_normal_test.num列的值生成。

1.記錄鎖測試
??1)主鍵索引
#開啟事務(wù)1
BEGIN;

#事務(wù)1,查詢id = 5的數(shù)據(jù)并加鎖
SELECT * FROM t_num_test WHERE id = 5 FOR UPDATE;


#開啟事務(wù)2,插入?yún)^(qū)間(1,5]
INSERT INTO t_num_test VALUES(2,201);
#正常執(zhí)行

#開啟事務(wù)3,插入?yún)^(qū)間(5,9]
INSERT INTO t_num_test VALUES(6,600);
#正常執(zhí)行

#開啟事務(wù)4,更新鎖定行
UPDATE t_num_test SET num = 10000 WHERE id = 5;
#阻塞

#提交事務(wù)
COMMIT;  

根據(jù)上述案例可知,在使用主鍵索引id進(jìn)行精準(zhǔn)查詢,只鎖定一條記錄時,MySQL加的是記錄鎖,不會產(chǎn)生間隙鎖

注:唯一索引在此場景下作用效果與主鍵索引相同。

??2)普通索引
#開啟事務(wù)1
BEGIN;
#事務(wù)1,查詢id = 5的數(shù)據(jù)并加鎖
SELECT * FROM t_num_normal_test WHERE num = 5 FOR UPDATE;

#開啟事務(wù)2,插入?yún)^(qū)間(1,5]
INSERT INTO t_num_normal_test VALUES(2,2);
#阻塞

#開啟事務(wù)3,插入?yún)^(qū)間(5,9]
INSERT INTO t_num_normal_test VALUES(6,6);
#阻塞

#開啟事務(wù)4,插入?yún)^(qū)間(9,13]
INSERT INTO t_num_normal_test VALUES(10,10);
#正常執(zhí)行

#開啟事務(wù)4,更新鎖定行
UPDATE t_num_normal_test SET num = 10000 WHERE id = 5;
#阻塞

#提交事務(wù)
COMMIT;

不難發(fā)現(xiàn),當(dāng)插入數(shù)據(jù)至區(qū)間(1,5]和區(qū)間(5,9]時事務(wù)發(fā)生阻塞,插入數(shù)據(jù)至區(qū)間(9,13]時事務(wù)正常執(zhí)行了,也就是說在區(qū)間(1,5]和區(qū)間(5,9]產(chǎn)生了間隙鎖;在更新鎖定行時事務(wù)發(fā)生阻塞,在該行產(chǎn)生了記錄鎖。

根據(jù)上述案例可知,在使用普通索引指定num的值查詢時,MySQL在行上加記錄鎖,在該行相鄰區(qū)間加間隙鎖,而記錄鎖與間隙鎖的組合組成了臨鍵鎖,即使用了臨鍵鎖。

2.間隙鎖測試
??1)主鍵索引
#開啟事務(wù)1
BEGIN;
#事務(wù)1,鎖定區(qū)間(5,9]
SELECT * FROM t_num_test WHERE id BETWEEN 5 AND 9 FOR UPDATE;

#開啟事務(wù)2,插入?yún)^(qū)間(1,5]
INSERT INTO t_num_test VALUES(2,201);
#正常執(zhí)行

#開啟事務(wù)3,插入?yún)^(qū)間(5,9]
INSERT INTO t_num_test VALUES(6,600);
#阻塞

#開啟事務(wù)4,插入?yún)^(qū)間(9,13]
INSERT INTO t_num_test VALUES(10,1000);  
#正常執(zhí)行

#開啟事務(wù)5,更新id = 5的記錄
UPDATE t_num_test SET num = 10000 WHERE id = 5;
#阻塞

#開啟事務(wù)6,更新id = 9的記錄
UPDATE t_num_test SET num = 10000 WHERE id = 9;
#阻塞

#提交事務(wù)
COMMIT;

測試結(jié)果:當(dāng)給區(qū)間(5,9]加鎖時,向區(qū)間(5,9]插入數(shù)據(jù)的事務(wù)被阻塞,向區(qū)間(1,5],(9,13]插入數(shù)據(jù)的事務(wù)正常執(zhí)行了,所以MySQL在區(qū)間(5,9]上產(chǎn)生了間隙鎖。在向id為5和9的兩條記錄執(zhí)行更新操作時,事務(wù)被阻塞了,說明在兩條數(shù)據(jù)記錄上添加了記錄鎖。

由上可知,根據(jù)主鍵索引鎖定一個區(qū)間時,MySQL會在該區(qū)間添加間隙鎖,在區(qū)間邊界處添加記錄鎖。

注:唯一索引在此場景下作用效果與主鍵索引相同。

如果鎖定不存在的行會發(fā)生什么情況?

#開啟事務(wù)1
BEGIN;
#事務(wù)1,鎖定不存在的數(shù)據(jù)
SELECT * FROM t_num_test WHERE id = 7 FOR UPDATE;

#開啟事務(wù)2,插入?yún)^(qū)間(1,5]
INSERT INTO t_num_test VALUES(2,201);
#正常執(zhí)行

#開啟事務(wù)3,插入?yún)^(qū)間(5,9]
INSERT INTO t_num_test VALUES(6,600);
#阻塞

#開啟事務(wù)4,插入?yún)^(qū)間(9,13]
INSERT INTO t_num_test VALUES(10,1000);  
#正常執(zhí)行

#開啟事務(wù)5,更新id = 5的記錄
UPDATE t_num_test SET num = 10000 WHERE id = 5;
#正常執(zhí)行

#開啟事務(wù)6,更新id = 9的記錄
UPDATE t_num_test SET num = 10000 WHERE id = 9;
#正常執(zhí)行

#提交事務(wù)
COMMIT;

測試結(jié)果:在區(qū)間(5,9]產(chǎn)生間隙鎖。

當(dāng)鎖定不存在的數(shù)據(jù)時,會在該數(shù)據(jù)所在區(qū)間產(chǎn)生間隙鎖。

??2)普通索引

?????同記錄鎖普通索引

3.無索引列測試
#開啟事務(wù)1
BEGIN;
#事務(wù)1,無索引列加鎖
SELECT * FROM t_num_test WHERE num = 300 FOR UPDATE;

#開啟事務(wù)2,插入?yún)^(qū)間(1,5]
INSERT INTO t_num_test VALUES(2,201);
#阻塞

#開啟事務(wù)3,插入?yún)^(qū)間(5,9]
INSERT INTO t_num_test VALUES(6,600);
#阻塞

#開啟事務(wù)4,插入?yún)^(qū)間(9,13]
INSERT INTO t_num_test VALUES(10,1000);  
#阻塞

#開啟事務(wù)5,插入?yún)^(qū)間(13,+∞)
INSERT INTO t_num_test VALUES(14,2000,'kkk');
#阻塞

#提交事務(wù)
COMMIT;

表鎖。

總結(jié)

對主鍵索引或唯一索引來說,當(dāng)鎖定一條記錄時,會產(chǎn)生記錄鎖;當(dāng)鎖定一個區(qū)間時,會產(chǎn)生間隙鎖和記錄鎖,即臨鍵鎖。

對普通索引來說,會產(chǎn)生臨鍵鎖。

對無索引列來說,會鎖住整張數(shù)據(jù)表。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內(nèi)容