mysql之鎖與事務

180323_LOCK1.jpg

相關博文推薦:mysql

Mysql之鎖與事務

平時的業務中,頂多也就是寫寫簡單的sql,連事務都用的少,對鎖這一塊的瞭解就更加欠缺了,以前一個大神分享了下mysql的事務隔離級別,感受挺有意思的,正好發現一個很棒的博文,而後也收集了一些相關知識,正好來學習下,mysql中鎖與事務的神祕面紗,主要內容包括ios

  1. 共享鎖和排它鎖的區別以及適合範圍
  2. mysql的表鎖和行鎖的區別
  3. 怎麼判斷一個sql是否執行了鎖,執行的是表鎖仍是行鎖
  4. 事務是什麼,怎麼用
  5. 事務的特性ACID
  6. 事務的隔離級別 (RU, RC, RR, SER)
  7. 如何查看mysql使用的隔離級別

I. 鎖

在學習多線程時,咱們也常常會遇到鎖這個東西,那個時候談的比較多的是樂觀鎖和悲觀鎖,那這兩種鎖和DB中常說的共享鎖和獨佔鎖有什麼區別呢?先給出咱們已知的樂觀鎖和悲觀鎖定義git

  • 樂觀鎖:多線程中的CAS就是一種樂觀鎖,實際上不加鎖,先嚐試去執行,若是失敗則重試(或者根據失敗策略進行處理)
  • 悲觀鎖:上鎖,一次只能有一個線程訪問,其餘的都只能等待

1. 共享鎖和排它鎖

a. 共享鎖

突出在共享這個關鍵詞上,顧名思義,表示這個鎖能夠多人共享,通常又能夠稱爲讀鎖(S鎖)github

在DB中,讀鎖表示全部的讀取數據的小夥伴都不會被鎖阻塞,能夠放心大膽的獲取數據,專業一點的說法就是同一時刻,容許多個鏈接併發的讀取同一資源sql

b. 排它鎖

排它,表示當某我的持有這個鎖以後,其餘的人再來競爭鎖就會失敗,只能等待鎖釋放, 又稱爲寫鎖(X鎖)數據庫

在DB中,寫鎖表示同一時刻,只能有一個小夥伴操做,其餘的不論是讀仍是寫,都得排隊,專業說法是寫鎖會阻塞其餘的讀鎖或寫鎖請求,確保同一時刻只能有一個鏈接能夠寫入資源,並防止其餘鏈接讀取或者寫資源session

c. gapLock 和 next key lock

  • next key lock 主要是範圍匹配的場景下,會鎖某一個範圍區間
  • gapLock 主要用來鎖邊界

以下面的case(說明,columnA是非惟一索引,RR隔離級別)多線程

  • where columnA between 10 and 30, next key lock 確保不會在10, 30 以內插入新的數據行
  • where columnA = 10, gap lock 確保不會再次插入一個columnA=10的行

2. 表鎖和行鎖

對於DB的操做,一般會出現兩種狀況,一個是鎖表,一個鎖行併發

  • 表鎖:表示整個表被某一個鏈接佔用了寫鎖,致使其餘鏈接的讀鎖或者寫鎖都會阻塞;影響整個表的讀寫
  • 行鎖:表示表中某些行被某個鏈接佔用了寫鎖,可是其餘行,依然能夠被其餘的鏈接請求讀鎖、寫鎖;僅影響被鎖的那些行數據

那麼一個問題就來了,什麼sql會致使行鎖,什麼會致使寫鎖?甚至咱們如何判斷一個sql是否會請求鎖,請求的是讀鎖仍是寫鎖呢?hexo

3. 如何使用鎖

上面一節拋出了問題,那麼如今就是來看下如何使用和分析鎖了,首先咱們是咱們最多見的幾個sql

  • select
  • update
  • delete
  • insert

其中很容易得出的結論是 update, delete, insert 三個涉及到寫鎖;並且這種操做絕大部分的場景是操做具體的某些行(想一想爲何?),因此更常見的是行鎖

select讀操做則有點特殊

a. select分析

MVCC(multiple-version-concurrency-control)是個行級鎖的變種,它在普通讀狀況下避免了加鎖操做,所以開銷更低。即下面這個沒有讀鎖也沒有寫鎖

快照讀,不加鎖

select * from table ...
複製代碼

當前讀,select 語句能夠指定讀鎖和寫鎖,以下

-- 讀鎖
select * from table lock in share mode;

-- 寫鎖
select * from table for update;
複製代碼

說明,insert, update, delete 也是當前讀,理由以下:

1.update和delete操做流程分解:

  • 首先經過where條件查詢到第一個知足的記錄,並加鎖
  • 對這條記錄進行更新,再讀取下一條記錄
  • 對記錄更新,繼續讀下一條直到完畢

2.insert操做流程分解:

  • unique key 衝突檢測,會有一個當前讀
  • 無衝突時,插入

b. sql實例分析

--- SQL1:
select * from t1 where id = 10;

--- SQL2:
delete from t1 where id = 10;
複製代碼

在分析上面的sql以前,須要明確幾個前提:

  • id是否爲主鍵(id是否有索引)
  • 系統的隔離級別(隔離級別是什麼東西能夠先看下下文介紹)

分別說明:

case1: 主鍵+RC級別

  • sql1不加鎖,MySQL是使用多版本併發控制的,讀不加鎖
  • sql2加寫鎖(即X鎖),只鎖 id=10這一行

180323_LOCK2.jpg

case2: 惟一索引+rc級別

  • sql2加寫鎖,以下圖的case,就兩把鎖,一個對應於id unique索引上的id = 10的記錄,另外一把鎖對應於聚簇索引上的[name=’d’,id=10]的記錄

180323_LOCK3.jpg

case3: id非惟一索引+RC

  • sql2加寫鎖,以下圖的case,會有四個寫鎖

180323_LOCK4.jpg

case4: 無索引+RC

  • sql2分析:若id列上沒有索引,SQL會走聚簇索引的全掃描進行過濾,因爲過濾是由MySQL Server層面進行的。所以每條記錄,不管是否知足條件,都會被加上寫鎖(X鎖)。
  • 可是,爲了效率考量,MySQL作了優化,對於不知足條件的記錄,會在判斷後放鎖,最終持有的,是知足條件的記錄上的鎖,可是不知足條件的記錄上的加鎖/放鎖動做不會省

180323_LOCK5.jpg

case5: 主鍵+RR

加鎖同case1

case6: 惟一索引+RR

加鎖同case2

case7: 非惟一索引+RR

RR級別不容許出現幻讀,簡單來講,在加鎖的過程當中,不容許在新增or修改知足條件的記錄

即下圖中,除了圖三中相似的x鎖以外,還會新增一個gap鎖,這個gap鎖主要確保那幾個位置上不能插入新的記錄

180323_LOCK6.jpg

case8: 無索引+RR

  • 在Repeatable Read隔離級別下,若是進行全表掃描的當前讀,那麼會鎖上表中的全部記錄,同時會鎖上聚簇索引內的全部GAP,杜絕全部的併發 更新/刪除/插入 操做

180323_LOCK7.jpg

case9: Serializable級別

  • sql2: Serializable隔離級別。對於SQL2:delete from t1 where id = 10; 來講,Serializable隔離級別與Repeatable Read隔離級別徹底一致
  • SQL1: 在RC,RR隔離級別下,都是快照讀,不加鎖。可是在Serializable隔離級別,SQL1會加讀鎖,也就是說快照讀不復存在,MVCC併發控制降級爲Lock-Based CC

II. 事務

事務可謂是db中很是重要的一個知識點了,接下來咱們的目標就是弄懂什麼是事務,怎麼使用事務,以及事務與鎖之間的關聯是怎樣的

說明:本文的分析主要是以mysql的innordb存儲引擎爲標準

1. 定義

事務就是一組原子性的sql,或者說一個獨立的工做單元。

事務就是說,要麼mysql引擎會所有執行這一組sql語句,要麼所有都不執行(好比其中一條語句失敗的話)。

2. ACID特性

a. A:atomiciy 原子性

一個事務必須保證其中的操做要麼所有執行,要麼所有回滾,不可能存在只執行了一部分這種狀況出現。

b. C:consistency一致性

數據必須保證從一種一致性的狀態轉換爲另外一種一致性狀態。

c. I:isolation 隔離性

在一個事務未執行完畢時,一般會保證其餘Session 沒法看到這個事務的執行結果

d. D:durability 持久性

事務一旦commit,則數據就會保存下來,即便提交完以後系統崩潰,數據也不會丟失

3. 隔離級別

前面在分析鎖的sql時,就提到了隔離級別,一般有四種: RU, RC, RR, Serializable

在說明這個以前,先了解幾個概念

a. 基本概念

  • 髒讀:讀取到一個事務未提交的數據,由於這個事務最終沒法保證必定執行成功,那麼讀取到的數據就沒法保證必定準確
  • 不可重複讀:簡單來講就是在一個事務中讀取的數據可能產生變化,一樣的sql,在一個事務中執行屢次,可能獲得不一樣的結果
  • 幻讀:會話T1事務中執行一次查詢,而後會話T2新插入一行記錄,這行記錄剛好能夠知足T1所使用的查詢的條件。而後T1又使用相同 的查詢再次對錶進行檢索,可是此時卻看到了事務T2剛纔插入的新行
  • 加鎖讀:select * from table ... 的執行是否加了讀鎖 (這個能夠參考上面的sql加鎖分析)

b. RU: Read Uncommited 未提交讀

事務中的修改,即便沒有提交,對其餘會話也是可見的,即表示可能出現髒讀,通常數據庫都不採用這種方案

c. RC: Read Commited 提交讀

這個隔離級別保證了一個事務若是沒有徹底成功(commit執行完),事務中的操做對其餘會話是不可見的,避免了髒讀的可能

可是可能出現不可重複度的狀況,舉例說明:

  • 會話T1, 執行查詢 select * from where id=1,第一次返回一個結果
  • 會話T2, 執行修改 update table set updated=xxx where id=1 並提交
  • 會話T1,再次執行查詢 select * from where id=1,此次返回的結果中update字段就和前面的不同了

實際的生產環境中,這個級別用的比較多,特地查了下公司的db隔離級別就是這個

一個RC級別的演示過程:

  • 會話1,開啓事務,查詢
  • 會話2,開啓事務,更新DB,提交事務
  • 會話1,再次查詢,提交事務
  • 從下面的實際演示結果能夠知道,會話1,同一個sql,兩次執行的結果不一樣

180323_LOCK8.gif

相關的sql代碼以下:

-- 設置會話隔離級別
set session transaction ioslation read commited;

-- 查看當前會話隔離級別
select @@tx_isolation;

-- 會話1的操做
start transaction;
select * from newuser where userId=1;


-- 會話2開始操做
start transaction;
select * from newuser where userId=1;
update newuser set updated=1521786092 where userId=1;
select * from newuser where userId=1;
commit;


-- 再次進入會話1,一樣執行上次的sql,對比兩次輸出結果
select * from newuser where userId=1;

-- 注意觀察,會話1,先後兩次這個sql的輸出結果,特別是updated字段
-- 正常狀況會如上面的demo圖,會發生改變


-- 關閉會話
commit;

-- 再次查詢
select * from newuser where userId=1;
複製代碼

d. RR: Repeatable Read 可重複度

一個事務中屢次執行統一讀SQL,返回結果同樣。 這個隔離級別解決了髒讀的問題,幻讀問題

實例演示解決髒讀的過程(將上面的過程一樣來一次)

  • 發現無論會話1同一個sql,返回的結果都是相同的

180323_LOCK9.gif

e. Serializable 可串行化

最強的隔離級別,經過給事務中每次讀取的行加鎖,寫加寫鎖,保證不產生幻讀問題,可是會致使大量超時以及鎖爭用問題。

f. 經常使用命令

  • 查看當前會話隔離級別: select @@tx_isolation
  • 查看系統當前隔離級別: select @@global.tx_isolation
  • 設置當前會話隔離級別: set session transaction isolation level read committed;
  • 設置系統當前隔離級別: set global transaction isolation level read committed;
  • 命令行,
    • 開始事務: start transactioin;
    • 提交: commit;

4. 使用姿式

前面演示事務隔離級別的時候,給出的實例就演示了事務的使用姿式,通常做爲三步驟:

  • 開始事務 start transaction;
  • 執行你的業務sql
  • 提交事務 commit;

咱們如今演示如下一個事務中,讀鎖、寫鎖對另外一個事務的影響

a. 讀鎖的影響

咱們採用mysql默認的RR級別進行測試,userId爲主鍵

-- 會話1
start transaction;
select * from newuser where userId=1 lock in share mode;

-- 轉入會話2
start transaction;
select * from newuser where userId=1; -- 會輸出
select * from newuser where userId=1 lock in share mode; -- 會輸出
update newuser set updated=1521787137 where userId=1; -- 會掛起


-- 轉入會話1
-- 提交, 此時觀察會話2的寫是否完成
commit;

-- 轉入會話2
commit;
複製代碼

實際執行演示:

180323_LOCK10.gif

b. 寫鎖的影響

-- 會話1
start transaction;
select * from newuser where userId=1 for update;

-- 轉入會話2
start transaction;
select * from newuser where userId=1; -- 會輸出
select * from newuser where userId=1 lock in share mode; -- 會掛住

-- update newuser set updated=1521787137 where userId=1; -- 會掛住

-- 轉入會話1
-- 提交, 此時觀察會話2的寫是否完成
commit;

-- 轉入會話2
commit;
複製代碼

實際執行演示:

180323_LOCK11.gif

c. 小結

  • 讀鎖,會阻塞其餘請求寫鎖的sql執行
  • 寫鎖,會阻塞其餘讀鎖和寫鎖的sql執行
  • 事務只有在提交以後,纔會釋放鎖
  • 額外注意,上面事務在提交以後纔會釋放鎖,所以若是兩個事務循環依賴鎖時,可能發生死鎖

III. 小結

鎖和事務可謂是db中很是重要的知識點了,在咱們實際的編碼過程當中(通常針對mysql, innordb存儲引擎,rr隔離級別),作出下面的一些總結

1. sql分析

  • select * from table where xxx; (讀快照,通常不加鎖)
  • select * from table where xxx lock in share mode; (讀鎖,會阻塞其餘的寫鎖請求,但其餘的讀鎖請求沒有影響)
  • select * from table where xxx for update; (寫鎖,會阻塞其餘的讀寫請求)
  • update tableName set xxx (寫鎖)
  • insert (寫鎖)
  • delete (寫鎖)

2. 事務

簡單來說,事務就是一組sql,要麼所有執行成功,要麼所有失敗

四個特性: A(原子性)C(一致性)I(隔離性)D (持久性)

四種隔離級別:(mysql 默認採用的是RR級別)

隔離級別 髒讀 不可重複讀 幻讀 加鎖讀
read uncommited 可能 可能 可能
read commited 不可能 可能 可能
repeatable read 不可能 不可能 不可能
serializable 不可能 不可能 不可能

使用姿式:

start transaction;

-- xxx 具體的sql

commit;
複製代碼

IV. 其餘

參考

我的博客: 一灰灰Blog

基於hexo + github pages搭建的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

聲明

盡信書則不如,已上內容,純屬一家之言,因本人能力通常,見識有限,如發現bug或者有更好的建議,隨時歡迎批評指正

掃描關注

QrCode
相關文章
相關標籤/搜索