MySQL鎖機制


一、基本概念

  從操作的類型上來看,分為讀鎖和寫鎖:

    讀鎖:共享鎖,對同一份數據,多個讀操作可以同時進行且相互間不影響

    寫鎖:排它鎖,獨占資源。在當前操作未完成之前,其他寫操作必須等待。讀操作不影響。

       排它鎖作用於innodb,且必須在事務塊中執行。在進行事務操作時,for update會對結果集中的每一行數據加排它鎖,其他線程對於結果集中的數據進行修改操作,全部阻塞。

  從鎖數據的細粒度上來看,分為行鎖和表鎖。

二、測試

  測試環境:mysql 5.5.6、Navicat for mysql。

  新建表:

CREATE TABLE `tb_user` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  `password` varchar(10) DEFAULT NULL,
  `sex` char(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

  開啟兩個查詢會話,模擬多請求。

  1、表鎖

    鎖的粒度偏大,開銷小,鎖表快,但是發生鎖競爭的概率特別高,並發度低。

    對於更新update、delete、insert自動加寫鎖。也可如下的顯式命令加鎖:

    基本命令分析:

      加鎖:LOCK TABLE tablename WRITE/READ;

      釋放:UNLOCK TABLES;

      查詢表是否加鎖:show open tables;

      表鎖分析:SHOW STATUS LIKE 'table%'; 

         結果返回兩個參數:Table_locks_immediate表示產生表級鎖定的次數,表示可以立即獲取鎖的查詢次數,每立即獲取鎖值加1。

                  Table_locks_waited 出現表級鎖定爭用而發生等待的次數(不能立即獲取鎖的次數,每等待一次鎖值加1)。

    1.1、讀鎖

        不阻塞對加鎖表的讀操作,但是在當前會話中,不可對其他表查詢。其他會話的對加鎖表的更新,也會阻塞等待鎖釋放。

-- session01 加表級讀鎖
LOCK TABLE tb_user READ;

 -- session02

-- 查詢鎖定的表
SELECT * from tb_user;

-- 查詢鎖定的表
SELECT * from tb_user;
-- 查詢其他未鎖定表
SELECT * from tbl_user_mycat;

-- 查詢其他未鎖定表,報錯:[Err] 1100 - Table 'tbl_user_mycat' was not locked with LOCK TABLES
SELECT * from tbl_user_mycat;
-- 更新鎖定的表,報錯:[Err] 1099 - Table 'tb_user' was locked with a READ lock and can't be updated
UPDATE tb_user SET `name`='heihei';

-- 更新鎖定的表,會阻塞,直到鎖釋放后,再繼續完成執行操作
UPDATE tb_user SET `name`='heihei';

-- 釋放鎖
UNLOCK TABLES;

 

  上述更新完成。

    1.2、寫鎖

        其他會話中,不能對加鎖表進行讀寫操作。在釋放鎖之前,也不能對其他未加鎖表進行讀寫操作。

-- session01 加表級寫鎖
LOCK TABLE tb_user WRITE;

 -- session02

-- 查詢鎖定的表
SELECT * from tb_user;

-- 更新鎖定的表
UPDATE tb_user SET `name`='heihei';

 

-- 查詢其他未鎖定表
SELECT * from tbl_user_mycat;

 

-- 查詢其他未鎖定表,報錯:[Err] 1100 - Table 'tbl_user_mycat' was not locked with LOCK TABLES
SELECT * from tbl_user_mycat;

 

-- 更新鎖定的表,會阻塞,直到鎖釋放后,再繼續完成執行操作
UPDATE tb_user SET `name`='heihei';
-- 查詢鎖定的表,會阻塞,直到鎖釋放后,再繼續完成執行操作
SELECT * from tb_user;

 

-- 釋放鎖
UNLOCK TABLES;

 
   上述所有的對加鎖的表的讀寫操作,會執行完成

  2、行鎖

    鎖粒度較小(查詢結果集的記錄行),發生鎖競爭概率較低,並發度高。但是,可能會出現死鎖。通常,事務和行鎖是在確保數據准確的基礎上提高並發的處理能力。

    基本命令分析: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 :系統啟動后到現在總共等待的次數
    一般來說,關注Innodb_row_lock_waits(等待總次數)、Innodb_row_lock_time_avg(等待平均時長)比較高的時候,說明系統中競爭比較激烈,資源處理慢或者其他什么原因,具體再查詢分析結果,制定相關的優化。

    innodb在通過索引條件檢索數據的時候,會加行鎖。否則,都是加的表鎖。也就是說:innodb加行鎖是針對索引,而不是記錄行。沒有索引或者索引失效,都會升級為表鎖。

    對於update、delete和insert語句,innodb會自動的給結果集加排它鎖。select默認是不做任何操作的。當然,顯式的加鎖也是可以滴:

      共享鎖(讀鎖,多個讀鎖可同時進行,但是若事務中對讀鎖記錄做修改操作,很有可能會發生死鎖):SELECT * from tb_user where id=10010 LOCK IN SHARE MODE;

      排它鎖(寫鎖,在當前事務未commit之前,阻塞其他的讀鎖和寫鎖):SELECT * from tb_user where id=10010 FOR UPDATE;

    以下,2.1和2.2基於update來說明上述的自動加鎖機制。2.3和2.4舉例來說明顯式的共享鎖和排它鎖問題。

    2.1、對於索引列的where,加行鎖:

-- SESSION01 開啟事務
START TRANSACTION;
-- 更新操作,id為主鍵,加行鎖
update tb_user set `name`='testname' where id=10006;

 
 

-- SESSION02 開啟事務
START TRANSACTION;
-- 更新操作,id為主鍵,加行鎖。由於和session01鎖定不同,所以不阻塞,無需等待session01提交commit,直接執行
update tb_user set `name`='testname3' where id=10010;

COMMIT;  
  COMMIT;
   

    2.2、對於不是索引列的where,加表鎖:

-- SESSION01 開啟事務
START TRANSACTION;
-- 更新操作,name為非索引列
update tb_user set `password`='111' WHERE `name`='testname2';

 
 

-- SESSION02 開啟事務
START TRANSACTION;
-- 更新操作,name為非索引列,由於session01非索引列會鎖表,故下面更新會阻塞,等待session01提交commit
update tb_user set `password`='111' WHERE `name`='testname3';

COMMIT;  
  -- session01提交commit,上述更新update完成操作
  COMMIT;

    2.3、共享鎖

-- SESSION01 開啟事務
START TRANSACTION;
-- 查詢,顯式加共享鎖
SELECT * from tb_user where id=10010 LOCK IN SHARE MODE;

 
 

-- SESSION02 開啟事務
START TRANSACTION;
-- 讀鎖可直接執行,不阻塞
SELECT * from tb_user where id=10010 LOCK IN SHARE MODE;

-- 對讀鎖進行修改,等待session02提交commit,阻塞

UPDATE tb_user set name='testname1' WHERE id=10010;

 
 

-- 報錯:[Err] 1213 - Deadlock found when trying to get lock; try restarting transaction

UPDATE tb_user set name='testname1' WHERE id=10010;

-- 操作繼續。session02中,mysql判斷出現死鎖,回滾session02后,session01繼續操作

 

    2.4、排它鎖

-- SESSION01 開啟事務
START TRANSACTION;
-- 查詢,顯式加排它鎖
SELECT * from tb_user where id=10010 FOR UPDATE;

 
 

-- SESSION02 開啟事務
START TRANSACTION;
-- 排它鎖,阻塞等待session01提交commit,釋放鎖
SELECT * from tb_user where id=10010 FOR UPDATE;

-- 當前事務中可執行
UPDATE tb_user set name='testname' WHERE id=10010;

-- 排它鎖,阻塞等待session01提交commit,釋放鎖
UPDATE tb_user set name='testname2' WHERE id=10010;
COMMIT;

 

 

-- session01提交commit,上述阻塞操作繼續執行完成

COMMIT;

    2.5、間隙鎖

      當使用范圍查詢,且申請共享鎖或者排它鎖的時候,InnoDB會給符合條件的已有的結果集的索引加鎖。對於在范圍內但是不存在的的記錄值,叫做“間隙(GAP)”。InnoDB加鎖也會對這些間隙進行加鎖,成為間隙鎖。

      在鎖定一個很大的范圍之后,即使記錄不存在,也會被鎖定。這就導致,如果此時有其他插入這些不存在的記錄的請求,會一直阻塞等待。很容易對性能產生影響。

三、使用和總結

  1、Innodb默認使用的是行鎖。當未使用索引列查詢限定的時候會升級為表鎖。

  一般,在檢查分析鎖沖突的時候,必須explain查看sql的執行計划。因為mysql在執行過程中,並不是一定會使用索引(explain查看執行計划的時候,才會有possible_key和key),有可能的情況是mysql認為全表掃描更快,那么此時就不會用行鎖,而升級使用表鎖。

  2、Innodb默認自動給更新操作加鎖


免責聲明!

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



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