MySQL高級(七)、鎖機制

一、概述

定義

鎖是計算機協調多個進程或線程併發訪問某一資源的機制。在數據庫中,除傳統的計算資源(如CPU、RAM、I/O等)的爭用以外,數據也是一種供許多用戶共享的資源。如何保證數據併發訪問的一致性、有效性是所有數據庫必須解決的一個問題,鎖衝突也是影響數據庫併發訪問性能的一個重要因素。從這個角度來說,鎖對數據庫而言顯得尤其重要,也更加複雜。

生活案例

打個比方,我們到淘寶上買一件商品,商品只有一件庫存,這個時候如果還有另一個人買,
那麼如何解決是你買到還是另一個人買到的問題?

這裏肯定要用到事務,我們先從庫存表中取出物品數量,然後插入訂單,付款後插入付款表信息,
然後更新商品數量。在這個過程中,使用鎖可以對有限的資源進行保護,解決隔離和併發的矛盾。

鎖的分類

從對數據操作的類型(讀/寫)分:

  • 讀鎖(共享鎖):針對同一份數據,多個讀操作可以同時進行而不會互相影響。
  • 寫鎖(排它鎖):當前寫操作沒有完成前,他會阻斷其他寫鎖和讀鎖。

從對數據操作的粒度分:

  • 表鎖
  • 行鎖

二、表鎖

特點

偏向MyISAM存儲引擎,開銷小,加鎖快;無死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。
案例:
我們在創建mylock表,其中有一個id字段和name字段,並在該表上加鎖測試。該表結構如下所示。

create table mylock(
 id int not null primary key auto_increment,
 name varchar(20)
)engine myisam;

加讀鎖

lock table mylock read; #在當前會話session1爲mylock表添加讀鎖
show open tables; #查看錶加鎖情況,加上鎖的表在In_use列上會顯示1
unlock tables;#釋放表鎖

加鎖效果:讀阻塞寫
我們另開一個會話session2,並在這兩個會話上進行測試。使用lock table 表名 read/write的方式一次只能鎖定一個表,在給另一個表加讀鎖後當前表的讀鎖會釋放

  1. 當前session可以查詢該表記錄,其他session也可以查詢該表記錄;
  2. 當前session不能查詢未鎖定的表,其他session可以查詢未鎖定的表;
    在這裏插入圖片描述
  3. 當前session插入或者更新鎖定的表都會提示錯誤 ,其他session插入或者更新鎖定的表會一直等待獲得鎖。
    在這裏插入圖片描述
    在這裏插入圖片描述

加寫鎖

  1. 當前session可以查詢、修改、插入該表記錄,其他session對該表的一切操作(包括:查詢、修改、插入)都會阻塞;
  2. 當前session不能查詢未鎖定的表,其他session可以查詢、修改、插入未鎖定的表。

結論

鎖類型 自己可讀 自己可寫 他人可讀 他人可寫
讀鎖 × ×
寫鎖 × ×

結合上表,所以對MyISAM表進行操作,會有以下情況:
1、對MyISAM表的讀操作(加讀鎖),不會阻塞其他進程對同一表的讀請求,但會阻塞對同一表的寫請求。只有當讀鎖釋放後,纔會執行其它進程的寫操作。
2、對MyISAM表的寫操作(加寫鎖),會阻塞其他進程對同一表的讀和寫操作,只有當寫鎖釋放後,纔會執行其它進程的讀寫操作。

簡而言之,就是讀鎖會阻塞寫,但是不會堵塞讀。而寫鎖則會把讀和寫都堵塞

三、行鎖

特點

偏向InnoDB存儲引擎,開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
InnoDB與MyISAM的最大不同有兩點:一是支持事務(TRANSACTION);二是採用了行級鎖
行鎖支持事務,複習一下老知識:
事務是由一組SQL語句組成的邏輯處理單元,事務具有以下4個屬性,通常簡稱爲事務的ACID屬性。

  • 原子性(Atomicity):事務是一個原子操作單元,其對數據的修改,要麼全都執行,要麼全都不執行。
  • 一致性(Consistent):在事務開始和完成時,數據都必須保持一致狀態。這意味着所有相關的數據規則都必須應用於事務的修改,以保持數據的完整性;事務結束時,所有的內部數據結構(如B樹索引或雙向鏈表)也都必須是正確的。
  • 隔離性(Isolation):數據庫系統提供一定的隔離機制,保證事務在不受外部併發操作影響的“獨立”環境執行。這意味着事務處理過程中的中間狀態對外部是不可見的,反之亦然。
  • 持久性(Durable):事務完成之後,它對於數據的修改是永久性的,即使出現系統故障也能夠保持。

隔離級別:
在這裏插入圖片描述
MySQL默認的隔離級別爲可重複讀(Repeatable read)。

案例

創建一個test_innodb_lock的表,其中帶有兩個字段int型的a和varchar型的b,建表語句如下所示:

create table test_innodb_lock (a int(11),b varchar(16))engine=innodb;
 
insert into test_innodb_lock values(1,'b2');
insert into test_innodb_lock values(3,'3');
insert into test_innodb_lock values(4,'4000');
insert into test_innodb_lock values(5,'5000');
insert into test_innodb_lock values(6,'6000');
insert into test_innodb_lock values(7,'7000');
insert into test_innodb_lock values(8,'8000');
insert into test_innodb_lock values(9,'9000');
insert into test_innodb_lock values(1,'b1');
 
create index test_innodb_a_ind on test_innodb_lock(a); #注意,行鎖一定是要有索引的
create index test_innodb_b_ind on test_innodb_lock(b);

演示過程:
step1:
開啓兩個會話session1和session2,並且都取消自動提交
在這裏插入圖片描述
step2:
在session1修改表中a=4的記錄,未提交的情況下,在session2中同樣修改a=4的記錄,查看結果
session1未提交:
在這裏插入圖片描述
session2阻塞:
在這裏插入圖片描述
step3:
在session1提交完成後,session2自動提交
session1提交:
在這裏插入圖片描述
session2自動提交:
在這裏插入圖片描述
step4:
session1再次提交後查看
在這裏插入圖片描述
step5:
在session1中修改第5條記錄,在session2中修改第9條記錄
在這裏插入圖片描述
在這裏插入圖片描述
這者互不干擾,可以順利修改,分別提交後,查看到兩條記錄都修改了。
在這裏插入圖片描述
在該案例中有兩點需要注意:

  • 因爲關閉了自動提交,所以要查看另一個會話已提交的修改,需要先commit;
  • 無索引行鎖升級爲表鎖,如在修改b字段時,故意不寫單引號,因爲自動類型轉換,則索引失效,不能在兩個會話分別修改不同的行記錄。

間隙鎖

【什麼是間隙鎖】
當我們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)”,
InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖(GAP Lock)。

【危害】
因爲Query執行過程中通過過範圍查找的話,他會鎖定整個範圍內所有的索引鍵值,即使這個鍵值並不存在。
間隙鎖有一個比較致命的弱點,就是當鎖定一個範圍鍵值之後,即使某些不存在的鍵值也會被無辜的鎖定,而造成在鎖定的時候無法插入鎖定鍵值範圍內的任何數據。在某些場景下這可能會對性能造成很大的危害。

【案例】
我們在上述的案例上接着做實驗,查看間隙鎖的危害。
在這裏插入圖片描述
先查看該表記錄,如上圖所示,在a字段中缺少2的記錄,我們進行如下的修改
session1修改記錄但未提交:
在這裏插入圖片描述
session2中插入記錄,阻塞:
在這裏插入圖片描述

使用select加鎖

加共享鎖

共享鎖又稱讀鎖,是讀取操作創建的鎖。其他用戶可以併發讀取數據,但任何事務都不能對數據進行修改(獲取數據上的排他鎖),直到已釋放所有共享鎖。
如果事務T對數據A加上共享鎖後,則其他事務只能對A再加共享鎖,不能加排他鎖。獲准共享鎖的事務只能讀數據,不能修改數據。

語法
SELECT … LOCK IN SHARE MODE;
如:

#爲表的第8和9行加上共享鎖
select * from test_innodb_lock where a in (8, 9) lock in share mode;

在查詢語句後面增加 LOCK IN SHARE MODE ,Mysql會對查詢結果中的每行都加共享鎖,當沒有其他線程對查詢結果集中的任何一行使用排他鎖時,可以成功申請共享鎖,否則會被阻塞。其他線程也可以讀取使用了共享鎖的行,而且這些線程讀取的是同一個版本的數據。

加排他鎖

排他鎖又稱寫鎖,如果事務T對數據A加上排他鎖後,則其他事務不能再對A加任任何類型的封鎖。獲准排他鎖的事務既能讀數據,又能修改數據。

語法
SELECT … FOR UPDATE;

在查詢語句後面增加 FOR UPDATE ,Mysql會對查詢結果中的每行都加排他鎖,當沒有其他線程對查詢結果集中的任何一行使用排他鎖時,可以成功申請排他鎖,否則會被阻塞。

行鎖總結

使用show status like ‘innodb_row_lock%’;命令查看分析系統上的行鎖的爭奪情況
在這裏插入圖片描述
各參數說明:
Innodb_row_lock_current_waits:當前正在等待鎖定的數量;
Innodb_row_lock_time:從系統啓動到現在鎖定總時間長度;
Innodb_row_lock_time_avg:每次等待所花平均時間;
Innodb_row_lock_time_max:從系統啓動到現在等待最長的一次所花的時間;
Innodb_row_lock_waits:系統啓動後到現在總共等待的次數;

優化建議:

  • 儘可能讓所有數據檢索都通過索引來完成,避免無索引行鎖升級爲表鎖
  • 儘可能較少檢索條件,避免間隙鎖
  • 儘量控制事務大小,減少鎖定資源量和時間長度
  • 鎖住某行後,儘量不要去調別的行或表,趕緊處理被鎖住的行然後釋放掉鎖
  • 涉及相同表的事務,對於調用表的順序儘量保持一致
  • 在業務環境允許的情況下,儘可能低級別事務隔離
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章