一條簡單的更新語句,MySQL是如何加鎖的?

来源:https://www.cnblogs.com/dx520/archive/2019/11/22/11831080.html
-Advertisement-
Play Games

看如下一條sql語句: # table T (id int, name varchar(20)) delete from T where id = 10; MySQL在執行的過程中,是如何加鎖呢? 在看下麵這條語句: select * from T where id = 10; 那這條語句呢?其實這 ...


看如下一條sql語句:

# table T (id int, name varchar(20))
delete from T where id = 10

MySQL在執行的過程中,是如何加鎖呢?

在看下麵這條語句:

select * from T where id = 10

那這條語句呢?其實這其中包含太多知識點了。要回答這兩個問題,首先需要瞭解一些知識。

相關知識介紹

多版本併發控制

在MySQL預設存儲引擎InnoDB中,實現的是基於多版本的併發控制協議——MVCC(Multi-Version Concurrency Control)(註:與MVVC相對的,是基於鎖的併發控制,Lock-Based Concurrency Control)。其中MVCC最大的好處是:讀不加鎖,讀寫不衝突。在讀多寫少的OLTP應用中,讀寫不衝突是非常重要的,極大的提高了系統的併發性能,在現階段,幾乎所有的RDBMS,都支持MVCC。其實,MVCC就一句話總結:同一份數據臨時保存多個版本的一種方式,進而實現併發控制。

當前讀和快照讀

在MVCC併發控制中,讀操作可以分為兩類:快照讀與當前讀。

快照讀(簡單的select操作):讀取的是記錄中的可見版本(可能是歷史版本),不用加鎖。這你就知道第二個問題的答案了吧。

當前讀(特殊的select操作、insert、delete和update):讀取的是記錄中最新版本,並且當前讀返回的記錄都會加上鎖,這樣保證了了其他事務不會再併發修改這條記錄。

聚集索引

也叫做聚簇索引。在InnoDB中,數據的組織方式就是聚簇索引:完整的記錄,儲存在主鍵索引中,通過主鍵索引,就可以獲取記錄中所有的列。

最左首碼原則

也就是最左優先,這條原則針對的是組合索引和首碼索引,理解:

1、在MySQL中,進行條件過濾時,是按照向右匹配直到遇到範圍查詢(>,<,between,like)就停止匹配,比如說a = 1 and b = 2 and c > 3 and d = 4 如果建立(a, b, c, d)順序的索引,d是用不到索引的,如果建立(a, b, d, c)索引就都會用上,其中a,b,d的順序可以任意調整。

2、= 和 in 可以亂序,比如 a = 1 and b = 2 and c = 3 建立(a, b, c)索引可以任意順序,MySQL的查詢優化器會優化索引可以識別的形式。

兩階段鎖

傳統的RDMS加鎖的一個原則,就是2PL(Two-Phase Locking,二階段鎖)。也就是說鎖操作分為兩個階段:加鎖階段和解鎖階段,並且保證加鎖階段和解鎖階段不想交。也就是說在一個事務中,不管有多少條增刪改,都是在加鎖階段加鎖,在 commit 後,進入解鎖階段,才會全部解鎖。

隔離級別

MySQL/InnoDB中,定義了四種隔離級別:

Read Uncommitted:可以讀取未提交記錄。此隔離級別不會使用。

Read Committed(RC):針對當前讀,RC隔離級別保證了對讀取到的記錄加鎖(記錄鎖),存在幻讀現象。

Repeatable Read(RR):針對當前讀,RR隔離級別保證對讀取到的記錄加鎖(記錄鎖),同時保證對讀取的範圍加鎖,新的滿足查詢條件的記錄不能夠插入(間隙鎖),不存在幻讀現象。

Serializable:從MVCC併發控制退化為基於鎖的併發控制。不區別快照讀和當前讀,所有的讀操作都是當前讀,讀加讀鎖(S鎖),寫加寫鎖(X鎖)。在該隔離級別下,讀寫衝突,因此併發性能急劇下降,在MySQL/InnoDB中不建議使用。

 Gap鎖和Next-Key鎖

在InnoDB中完整行鎖包含三部分:

記錄鎖(Record Lock):記錄鎖鎖定索引中的一條記錄。

間隙鎖(Gap Lock):間隙鎖要麼鎖住索引記錄中間的值,要麼鎖住第一個索引記錄前面的值或最後一個索引記錄後面的值。

Next-Key Lock:Next-Key鎖時索引記錄上的記錄鎖和在記錄之前的間隙鎖的組合。

進行分析

瞭解完以上的小知識點,我們開始分析第一個問題。當看到這個問題的時候,你可能會毫不猶豫的說,加寫鎖啊。這答案也錯也對,因為已知條件太少。那麼有那些需要已知的前提條件呢?

  • 前提一:id列是不是主鍵?
  • 前提二:當前系統的隔離級別是什麼?
  • 前提三:id列如果不是主鍵,那麼id列上有沒有索引呢?
  • 前提四:id列上如果有二級索引,那麼是唯一索引嗎?
  • 前提五:SQL執行計劃是什麼?索引掃描?還是全表掃描

根據上面的前提條件,可以有九種組合,當然還沒有列舉完全。

  1. id列是主鍵,RC隔離級別
  2. id列是二級唯一索引,RC隔離級別
  3. id列是二級不唯一索引,RC隔離級別
  4. id列上沒有索引,RC隔離級別
  5. d列是主鍵,RR隔離級別
  6. id列是二級唯一索引,RR隔離級別
  7. id列是二級不唯一索引,RR隔離級別
  8. id列上沒有索引,RR隔離級別

 組合一:id主鍵 + RC

這個組合是分析最簡單的,到執行該語句時,只需要將主鍵id = 10的記錄加上X鎖。如下圖所示:

 

 

結論:id是主鍵是,此SQL語句只需要在id = 10這條記錄上加上X鎖即可。

組合二:id唯一索引 + RC

這個組合,id不是主鍵,而是一個Unique的二級索引鍵值。在RC隔離級別下,是怎麼加鎖的呢?看下圖:

 

由於id是Unique索引,因此delete語句會選擇走id列的索引進行where條件過濾,在找到id = 10的記錄後,首先會將Unique索引上的id = 10的記錄加上X鎖,同時,會根據讀取到的name列,回到主鍵索引(聚簇索引),然後將聚簇索引上的name = 'e' 對應的主鍵索引項加X鎖。

結論:若id列是Unique列,其上有Unique索引,那麼SQL需要加兩個X鎖,一個對應於id Unique索引上的id = 10的記錄,另一把鎖對應於聚簇索引上的(name = 'e', id = 10)的記錄。

 組合三:id不唯一索引+RC

該組合中,id列不在唯一,而是個普通索引,那麼當執行sql語句時,MySQL又是如何加鎖呢?看下圖:

 

由上圖可以看出,首先,id列索引上,滿足id = 10查詢的記錄,均加上X鎖。同時,這些記錄對應的主鍵索引上的記錄也加上X鎖。與組合er的唯一區別,組合二最多只有一個滿足條件的記錄,而在組合三中會將所有滿足條件的記錄全部加上鎖。

結論:若id列上有非唯一索引,那麼對應的所有滿足SQL查詢條件的記錄,都會加上鎖。同時,這些記錄在主鍵索引上也會加上鎖。

組合四:id無索引+RC

相對於前面的組合,該組合相對特殊,因為id列上無索引,所以在 where id = 10 這個查詢條件下,沒法通過索引來過濾,因此只能全表掃描做過濾。對於該組合,MySQL又會進行怎樣的加鎖呢?看下圖:

 

由於id列上無索引,因此只能走聚簇索引,進行全表掃描。由圖可以看出滿足條件的記錄只有兩條,但是,聚簇索引上的記錄都會加上X鎖。但在實際操作中,MySQL進行了改進,在進行過濾條件時,發現不滿足條件後,會調用 unlock_row 方法,把不滿足條件的記錄放鎖(違背了2PL原則)。這樣做,保證了最後滿足條件的記錄加上鎖,但是每條記錄的加鎖操作是不能省略的。

結論:若id列上沒有索引,MySQL會走聚簇索引進行全表掃描過濾。由於是在MySQl Server層面進行的。因此每條記錄無論是否滿足條件,都會加上X鎖,但是,為了效率考慮,MySQL在這方面進行了改進,在掃描過程中,若記錄不滿足過濾條件,會進行解鎖操作。同時優化違背了2PL原則。

組合五:id主鍵+RR

該組合為id是主鍵,Repeatable Read隔離級別,針對於上述的SQL語句,加鎖過程和組合一(id主鍵+RC)一致。

組合六:id唯一索引+RR

該組合與組合二的加鎖過程一致。

組合七:id不唯一索引+RR

在組合一到組合四中,隔離級別是Read Committed下,會出現幻讀情況,但是在該組合Repeatable Read級別下,不會出現幻讀情況,這是怎麼回事呢?而MySQL又是如何給上述語句加鎖呢?看下圖:

 

該組合和組合三看起來很相似,但差別很大,在改組合中加入了一個間隙鎖(Gap鎖)。這個Gap鎖就是相對於RC級別下,RR級別下不會出現幻讀情況的關鍵。實質上,Gap鎖不是針對於記錄本身的,而是記錄之間的Gap。所謂幻讀,就是同一事務下,連續進行多次當前讀,且讀取一個範圍內的記錄(包括直接查詢所有記錄結果或者做聚合統計), 發現結果不一致(標準檔案一般指記錄增多, 記錄的減少應該也算是幻讀)。

那麼該如何解決這個問題呢?如何保證多次當前讀返回一致的記錄,那麼就需要在多個當前讀之間,其他事務不會插入新的滿足條件的記錄並提交。為了實現該結果,Gap鎖就應運而生。

如圖所示,有些位置可以插入新的滿足條件的記錄,考慮到B+樹的有序性,滿足條件的記錄一定是具有連續性的。因此會在 [4, b], [10, c], [10, d], [20, e] 之間加上Gap鎖。

Insert操作時,如insert(10, aa),首先定位到 [4, b], [10, c]間,然後插入在插入之前,會檢查該Gap是否加鎖了,如果被鎖上了,則Insert不能加入記錄。因此通過第一次當前讀,會把滿足條件的記錄加上X鎖,還會加上三把Gap鎖,將可能插入滿足條件記錄的3個Gap鎖上,保證後續的Insert不能插入新的滿足 id = 10 的記錄,也就解決了幻讀問題。

而在組合五,組合六中,同樣是RR級別,但是不用加上Gap鎖,在組合五中id是主鍵,組合六中id是Unique鍵,都能保證唯一性。一個等值查詢,最多只能返回一條滿足條件的記錄,而且新的相同取值的記錄是無法插入的。

結論:在RR隔離級別下,id列上有非唯一索引,對於上述的SQL語句;首先,通過id索引定位到第一條滿足條件的記錄,給記錄加上X鎖,並且給Gap加上Gap鎖,然後在主鍵聚簇索引上滿足相同條件的記錄加上X鎖,然後返回;之後讀取下一條記錄重覆進行。直至第一條出現不滿足條件的記錄,此時,不需要給記錄加上X鎖,但是需要給Gap加上Gap鎖嗎,最後返回結果。

組合八:id無索引+RR

該組合中,id列上無索引,只能進行全表掃描,那麼該如何加鎖,看下圖:

 

如圖,可以看出這是一個很恐怖的事情,全表每條記錄要加X鎖,每個Gap加上Gap鎖,如果表上存在大量數據時,又是什麼情景呢?這種情況下,這個表,除了不加鎖的快照讀,其他任何加鎖的併發SQL,均不能執行,不能更新,刪除,插入,這樣,全表鎖死。

當然,和組合四一樣,MySQL進行了優化,就是semi-consistent read。semi-consistent read開啟的情況下,對於不滿足條件的記錄,MySQL會提前放鎖,同時Gap鎖也會釋放。而semi-consistent read是如何觸發:要麼在Read Committed隔離級別下;要麼在Repeatable Read隔離級別下,設置了 innodb_locks_unsafe_for_binlog 參數。

結論:在Repeatable Read隔離級別下,如果進行全表掃描的當前讀,那麼會鎖上表上的所有記錄,並且所有的Gap加上Gap鎖,杜絕所有的 delete/update/insert 操作。當然在MySQL中,可以觸發 semi-consistent read來緩解鎖開銷與併發影響,但是semi-consistent read本身也會帶來其他的問題,不建議使用。

組合九:Serializable

在最後組合中,對於上訴的刪除SQL語句,加鎖過程和組合八一致。但是,對於查詢語句(比如select * from T1 where id = 10)來說,在RC,RR隔離級別下,都是快照讀,不加鎖。在Serializable隔離級別下,無論是查詢語句也會加鎖,也就是說快照讀不存在了,MVCC降級為Lock-Based CC。

結論:在MySQL/InnoDB中,所謂的讀不加鎖,並不適用於所有的情況,而是和隔離級別有關。在Serializable隔離級別下,所有的操作都會加鎖。

一條簡單的刪除語句加鎖情況也就分析完成了,但是學習不止於此,還在繼續,對於複雜SQL語句又是如何加鎖的呢?MySQL中的索引的分析又是怎樣的呢?性能分析、性能優化這些又是怎麼呢?請看後續。


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

-Advertisement-
Play Games
更多相關文章
  • 之前寫過一篇博客“SQL SERVER中關於OR會導致索引掃描或全表掃描的淺析”,裡面介紹了OR可能會引起全表掃描或索引掃描的各種案例,以及如何優化查詢條件中含有OR的SQL語句的幾種方法,其實還有一些方法可以用來優化這種問題,這裡簡單介紹一下。 如下所示,下麵的SQL語句之所有出現這種寫法,是因為... ...
  • 下載鏈接:https://pan.baidu.com/s/1uPbBknyIebQRDt4k_RA58Q 提取碼:14zi 將下載文件進行解壓,我解壓位置為:D:\Program Files\mysql-5.7.28-winx64 在D:\Program Files\mysql-5.7.28-win ...
  • 1.我們使用緩存時的業務流程大概為: 當我們查詢一條數據時,先去查詢緩存,如果緩存有就直接返回,如果沒有就去查詢資料庫,然後返回。這種情況下就可能出現下麵的一些現象。 2.緩存穿透 2.1什麼是緩存穿透 緩存穿透是指查詢一個一定不存在的數據,由於緩存是不命中時被動寫的,並且出於容錯考慮,如果從存儲層 ...
  • 如果你的系統有高併發的要求,可以嘗試使用SQL Server記憶體優化表來提升你的系統性能。你甚至可以把它當作Redis來使用。 ...
  • 對比結論 1. 性能上: 性能上都很出色,具體到細節,由於Redis只使用單核,而Memcached可以使用多核,所以平均每一個核上Redis在存儲小數據時比Memcached性能更高。而在100k以上的數據中,Memcached性能要高於Redis,雖然Redis最近也在存儲大數據的性能上進行優化 ...
  • select * from table1 t where (select count(*) from table1 where column1=t.column1 AND column2=t.column2 and column3=t.column3)>1 ...
  • USE [SPECIAL_BLD]GO SET ANSI_NULLS ONGO SET QUOTED_IDENTIFIER ONGO CREATE FUNCTION [dbo].[get_upper] ( @num numeric(18,5))RETURNS VARCHAR(500)ASBEGIN ...
  • 一、Atlas是什麼? 在當今大數據的應用越來越廣泛的情況下,數據治理一直是企業面臨的巨大問題。 大部分公司只是單純的對數據進行了處理,而數據的血緣,分類等等卻很難實現,市場上也急需要一個專註於數據治理的技術框架,這時Atlas應運而生。 Atlas官網地址: "https://atlas.apac ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...