MySQL鎖機制

1、基本概念javascript

  從操做的類型上來看,分爲讀鎖和寫鎖:java

    讀鎖:共享鎖,對同一份數據,多個讀操做能夠同時進行且相互間不影響mysql

    寫鎖:排它鎖,獨佔資源。在當前操做未完成以前,其餘寫操做必須等待。讀操做不影響。sql

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

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

2、測試高併發

  測試環境:mysql 5.5.六、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;

  開啓兩個查詢會話,模擬多請求。優化

  一、表鎖

    鎖的粒度偏大,開銷小,鎖錶快,可是發生鎖競爭的機率特別高,併發度低。

    對於更新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.一、讀鎖

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

-- 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.二、寫鎖

        其餘會話中,不能對加鎖表進行讀寫操做。在釋放鎖以前,也不能對其餘未加鎖表進行讀寫操做。

-- 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;

 
   上述全部的對加鎖的表的讀寫操做,會執行完成

  二、行鎖

    鎖粒度較小(查詢結果集的記錄行),發生鎖競爭機率較低,併發度高。可是,可能會出現死鎖。一般,事務和行鎖是在確保數據準確的基礎上提升併發的處理能力。

    基本命令分析: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.一、對於索引列的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.二、對於不是索引列的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.三、共享鎖

-- 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.四、排它鎖

-- 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.五、間隙鎖

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

      在鎖定一個很大的範圍以後,即便記錄不存在,也會被鎖定。這就致使,若是此時有其餘插入這些不存在的記錄的請求,會一直阻塞等待。很容易對性能產生影響。

3、使用和總結

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

  通常,在檢查分析鎖衝突的時候,必須explain查看sql的執行計劃。由於mysql在執行過程當中,並非必定會使用索引(explain查看執行計劃的時候,纔會有possible_key和key),有可能的狀況是mysql認爲全表掃描更快,那麼此時就不會用行鎖,而升級使用表鎖。

  二、Innodb默認自動給更新操做加鎖

相關文章
相關標籤/搜索