MySQL中的鎖


1. 

1.1. 鎖的簡介

1.1.1. 為什么需要鎖?

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

 

1.1.2. 鎖的概念

l  鎖是計算機協調多個進程或線程並發訪問某一資源的機制。

在數據庫中,數據也是一種供許多用戶共享的資源。如何保證數據並發訪問的一致性、有效性是所有數據庫必須解決的一個問題,鎖沖突也是影響數據庫並發訪問性能的一個重要因素。

鎖對數據庫而言顯得尤其重要,也更加復雜。

 

 

1.1.3. MySQL中的鎖

表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,並發度最低。

行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,並發度也最高。

頁面鎖(gap,間隙鎖):開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,並發度一般。

 

 在這個部分只講表級鎖行級鎖gap鎖放到事務中講

 

1.1.4. 表鎖與行鎖的使用場景

表級鎖更適合於以查詢為主,只有少量按索引條件更新數據的應用,如OLAP系統

 

行級鎖則更適合於有大量按索引條件並發更新少量不同數據,同時又有並發查詢的應用,如一些在線事務處理(OLTP)系統。

 

很難籠統地說哪種鎖更好,只能就具體應用的特點來說哪種鎖更合適

 

1.2. MyISAM

MySQL的表級鎖有兩種模式:

表共享讀鎖(Table Read Lock

表獨占寫鎖(Table Write Lock

 

 

 

1.2.1. 共享讀鎖

語法:lock table 表名 read

 

1. lock table testmysam READ  啟動另外一個session   select * from testmysam 可以查詢

2. insert into testmysam value(2);

    update testmysam set id=2  where id=1;

   報錯

3.在另外一個session

    insert into testmysam value(2);  等待

4.在同一個session

   insert into account value(4,'aa',123); 報錯

   select  * from account  ; 報錯

5.在另外一個session

   insert into account value(4,'aa',123); 成功

6.加索在同一個session select s.* from  testmysam s 報錯  

    lock table 表名 as 別名 read;

   

1.2.2. 獨占寫鎖

1.lock table testmysam WRITE

 在同一個session中  

   insert testmysam value(3);

  delete from testmysam where id = 3

  select * from testmysam

 

2.對不同的表操作(報錯)

select s.* from  testmysam s

insert into account value(4,'aa',123);

 

3.在其他session中 (等待)

select * from testmysam

1.2.3. 總結:

 

l 讀鎖,MyISAM表的讀操作,不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一表的寫請求

 

l 讀鎖,MyISAM表的讀操作,不會阻塞當前session對表讀,當對表進行修改會報錯

 

l 讀鎖,一個session使用LOCK TABLE命令給表f加了讀鎖,這個session可以查詢鎖定表中的記錄,但更新或訪問其他表都會提示錯誤;

 

l 寫鎖, MyISAM表的寫操作,則會阻塞其他用戶對同一表的讀和寫操作;

 

l 寫鎖, MyISAM表的寫操作,當前session可以對本表做CRUD,但對其他表進行操作會報錯

 

1.3. InnoDB

mysql InnoDB引擎支持行鎖

 

共享鎖又稱:讀鎖。當一個事務對某幾行上讀鎖時,允許其他事務對這幾行進行讀操作,但不允許其進行寫操作,也不允許其他事務給這幾行上排它鎖,但允許上讀鎖。

 

排它鎖又稱:寫鎖。當一個事務對某幾個上寫鎖時,不允許其他事務寫,但允許讀。更不允許其他事務給這幾行上任何鎖。包括寫鎖。

 

1.3.1. 語法

 

上共享鎖的寫法:lock in share mode

 

例如: select  *  from where  條件  lock in share mode

上排它鎖的寫法:for update

例如:select *  from 表  where 條件 for update

 

1.3.2. 注意:

 

1.兩個事務不能鎖同一個索引。

 

2.insert delete update在事務中都會自動默認加上排它鎖。

 

3.行鎖必須有索引才能實現,否則會自動鎖全表,那么就不是行鎖了。

 

CREATE TABLE testdemo (

`id`  int(255) NOT NULL ,

`c1`  varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,

`c2`  int(50) NULL DEFAULT NULL ,

PRIMARY KEY (`id`),

INDEX `idx_c2` (`c2`) USING BTREE

)

ENGINE=InnoDB;

 

insert into testdemo VALUES(1,'1',1),(2,'2',2);

 

 

 

1.

BEGIN

select * from testdemo where id =1 for update

在另外一個session

update testdemo set c1 = '1' where id = 2 成功

update testdemo set c1 = '1' where id = 1 等待

 

2.BEGIN

update testdemo set c1 = '1' where id = 1

在另外一個session

update testdemo set c1 = '1' where id = 1 等待

 

3.

BEGIN

update testdemo set c1 = '1' where  c1 = '1'

在另外一個session

update testdemo set c1 = '2' where  c1 = '2' 等待

 

4.第一個session

select * from testdemo where id =1 for update

 

第二個session

select * from testdemo where id =1 lock in share mode

 

回到第一個session  UNLOCK TABLES  並不會解鎖

使用commit 或者 begin或者ROLLBACK 才會解鎖

 

5.再來看下表鎖

lock table testdemo  WRITE

使用commitROLLBACK 並不會解鎖

使用UNLOCK TABLES 或者begin會解鎖

 

1.4. 鎖的等待問題

那么現在來說一個實際的問題,在工作中經常一個數據被鎖住,導致另外的操作完全進行不下去。

 

你肯定碰到過這問題,有些程序員在debug程序的時候,經常會鎖住一部分數據庫的數據,而這個時候你也要調試這部分功能,卻發現代碼總是運行超時,你是否碰到過這問題了,其實這問題的根源我相信你也知道了。

 

舉例來說,有兩個會話。

 

程序員甲,正直調試代碼

BEGIN

SELECT * FROM testdemo WHERE id = 1 FOR UPDATE

 

你正直完成的功能也要經過那部分的代碼,你得上個讀鎖

BEGIN

SELECT * FROM testdemo WHERE id = 1 lock in share mode

 

這個時候很不幸,你並不知道發生了什么問題,在你調試得過程中永遠就是一個超時得異常,而這種問題不管在開發中還是在實際項目運行中都可能會碰到,那么怎么排查這個問題呢?

這其實也是有小技巧的。

 

select * from information_schema.INNODB_LOCKS;

 

 

 

 

真好,我通過這個sql語句起碼發現在同一張表里面得同一個數據有了2個鎖其中一個是X(寫鎖),另外一個是S(讀鎖),我可以跳過這一條數據,使用其他數據做調試

 

可能如果我就是繞不過,一定就是要用這條數據呢?吼一嗓子吧(哪個缺德的在debug這個表,請不要鎖這不動),好吧,這是個玩笑,其實還有更好的方式來看

 

select * from sys.innodb_lock_waits

 

 

 

我現在執行的這個sql語句有了,另外看下最下面,kill命令,你在工作中完全可以通過kill吧阻塞了的sql語句給干掉,你就可以繼續運行了,不過這命令也要注意使用過,如果某同事正在做比較重要的調試,你kill,被他發現可能會被暴打一頓。

 

上面的解決方案不錯,但如果你的MySQL不是5.7的版本呢?是5.6呢,你根本就沒有sys庫,這個時候就難辦了,不過也是有辦法的。

SELECT

  r.trx_id waiting_trx_id,

  r.trx_mysql_thread_id waiting_thread,

  r.trx_query waiting_query,

  b.trx_id blocking_trx_id,

  b.trx_mysql_thread_id blocking_thread

FROM

  information_schema.innodb_lock_waits w

INNER JOIN

  information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id

INNER JOIN

  information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;

 

 

 

看到沒有,接下來你是否也可以執行kill 29 這樣的大招了。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM