MySQL(十):MySQL鎖機制

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

二. 數據庫鎖的分類

  • 從對數據操作的類型(讀\寫)分
    讀鎖(共享鎖):針對同一份數據,多個讀操作可以同時進行而不會互相影響。
    寫鎖(排它鎖):當前寫操作沒有完成前,它會阻斷其他寫鎖和讀鎖。
  • 從對數據操作的粒度分
    表鎖 & 行鎖
    爲了儘可能提高數據庫的併發度,每次鎖定的數據範圍越小越好,理論上每次只鎖定當前操作的數據的方案會得到最大的併發度,但是管理鎖是很耗資源的事情(涉及獲取,檢查,釋放鎖等動作),因此數據庫系統需要在高併發響應和系統性能兩方面進行平衡,這樣就產生了“鎖粒度(Lock granularity)”的概念。
    一種提高共享資源併發性的方式是讓鎖定對象更有選擇性。儘量只鎖定需要修改的部分數據,而不是所有的資源。更理想的方式是,只對會修改的數據片進行精確的鎖定。任何時候,在給定的資源上,鎖定的數據量越少,則系統的併發程度越高,只要相互之間不發生衝突即可。

三. 表鎖(偏讀)
偏向MyISAM存儲引擎,開銷小,加鎖快;無死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。

【表級鎖分析--建表SQL】 
create table mylock(
 id int not null primary key auto_increment,
 name varchar(20)
)engine myisam;
 
insert into mylock(name) values('a');
insert into mylock(name) values('b');
insert into mylock(name) values('c');
insert into mylock(name) values('d');
insert into mylock(name) values('e');
 
select * from mylock; 
【手動增加表鎖】
 lock table 表名字1 read(write),表名字2 read(write),其它;
【查看錶上加過的鎖】
 show open tables;
【釋放表鎖】
 unlock tables;
  • 加讀鎖 在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    session_1 ,session_2獲得表mylock的READ鎖定連接終端當前session可以查詢該表記錄,其他session也可以查詢該表的記錄 當前session不能查詢其它沒有鎖定的表 其他session可以查詢或者更新未鎖定的表 當前session中插入或者更新鎖定的表都會提示錯誤: 其他session插入或者更新鎖定表會一直等待獲得鎖: 釋放鎖 Session2獲得鎖,插入操作完成:
    在這裏插入圖片描述

  • 加寫鎖
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    session_1,session_2獲得表mylock的WRITE鎖定待Session1開啓寫鎖後,session2再連接終端當前session對鎖定表的查詢+更新+插入操作都可以執行: 其他session對鎖定表的查詢被阻塞,需要等待鎖被釋放: 在鎖表前,如果session2有數據緩存,鎖表以後,在鎖住的表不發生改變的情況下session2可以讀出緩存數據,一旦數據發生改變,緩存將失效,操作將被阻塞住。釋放鎖 Session2獲得鎖,查詢返回。

  • 結論
    MyISAM在執行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,在執行增刪改操作前,會自動給涉及的表加寫鎖。
    MySQL的表級鎖有兩種模式:
    表共享讀鎖(Table Read Lock)
    表獨佔寫鎖(Table Write Lock)
    在這裏插入圖片描述
    結合上表,所以對MyISAM表進行操作,會有以下情況:
    1、對MyISAM表的讀操作(加讀鎖),不會阻塞其他進程對同一表的讀請求,但會阻塞對同一表的寫請求。只有當讀鎖釋放後,纔會執行其它進程的寫操作。
    2、對MyISAM表的寫操作(加寫鎖),會阻塞其他進程對同一表的讀和寫操作,只有當寫鎖釋放後,纔會執行其它進程的讀寫操作。
    簡而言之,就是讀鎖會阻塞寫,但是不會堵塞讀。而寫鎖則會把讀和寫都堵塞。

  • 表鎖分析
    看看哪些表被加鎖了:show open tables;
    如何分析表鎖定?
    在這裏插入圖片描述
    四. 行鎖(偏寫)
    偏向InnoDB存儲引擎,開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
    InnoDB與MyISAM的最大不同有兩點:一是支持事務(TRANSACTION);二是採用了行級鎖。

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

  • 併發事務處理帶來的問題
    a. 更新丟失(Lost Update)
    當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,由於每個事務都不知道其他事務的存在,就會發生丟失更新問題--最後的更新覆蓋了由其他事務所做的更新。
    例如,兩個程序員修改同一java文件。每程序員獨立地更改其副本,然後保存更改後的副本,這樣就覆蓋了原始文檔。最後保存其更改副本的編輯人員覆蓋前一個程序員所做的更改。
    如果在一個程序員完成並提交事務之前,另一個程序員不能訪問同一文件,則可避免此問題。
    b. 髒讀(Dirty Reads)
    一個事務正在對一條記錄做修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態;這時,另一個事務也來讀取同一條記錄,如果不加控制,第二個事務讀取了這些“髒”數據,並據此做進一步的處理,就會產生未提交的數據依賴關係。這種現象被形象地叫做”髒讀”。
    一句話:事務A讀取到了事務B已修改但尚未提交的的數據,還在這個數據基礎上做了操作。此時,如果B事務回滾,A讀取的數據無效,不符合一致性要求。
    c. 不可重複讀(Non-Repeatable Reads)
    在一個事務內,多次讀同一個數據。在這個事務還沒有結束時,另一個事務也訪問該同一數據。那麼,在第一個事務的兩次讀數據之間。由於第二個事務的修改,那麼第一個事務讀到的數據可能不一樣,這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱爲不可重複讀,即原始讀取不可重複。
    一句話:一個事務範圍內兩個相同的查詢卻返回了不同數據。
    d. 幻讀(Phantom Reads)
    一個事務按相同的查詢條件重新讀取以前檢索過的數據,卻發現其他事務插入了滿足其查詢條件的新數據,這種現象就稱爲“幻讀”。
    一句話:事務A 讀取到了事務B提交的新增數據,不符合隔離性。

  • 事務隔離級別
    髒讀”、“不可重複讀”和“幻讀”,其實都是數據庫讀一致性問題,必須由數據庫提供一定的事務隔離機制來解決。
    在這裏插入圖片描述
    數據庫的事務隔離越嚴格,併發副作用越小,但付出的代價也就越大,因爲事務隔離實質上就是使事務在一定程度上 “串行化”進行,這顯然與“併發”是矛盾的。同時,不同的應用對讀一致性和事務隔離程度的要求也是不同的,比如許多應用對“不可重複讀”和“幻讀”並不敏感,可能更關心數據併發訪問的能力。
    常看當前數據庫的事務隔離級別:SHOW VARIABLES LIKE ‘transaction_isolation’;
    MySQL默認的事務隔離級別:REPEATABLE-READ

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_lock_b_ind on test_innodb_lock(b);

在這裏插入圖片描述

  • 無索引行鎖升級爲表鎖
    在這裏插入圖片描述
    Session_1Session_2正常情況,各自鎖定各自的行,互相不影響,一個2000另一個3000由於在column字段b上面建了索引,如果沒有正常使用,會導致行鎖變表鎖比如沒加單引號導致索引失效,行鎖變表鎖被阻塞,等待。直到Session_1提交後才阻塞解除,完成更新。

  • 間隙鎖危害
    在這裏插入圖片描述
    在這裏插入圖片描述
    Session_1Session_2阻塞產生,暫時不能插入,commit;阻塞解除,完成插入。
    當我們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)”,
    InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖(GAP Lock)。
    【危害】
    因爲Query執行過程中通過過範圍查找的話,他會鎖定整個範圍內所有的索引鍵值,即使這個鍵值並不存在。
    間隙鎖有一個比較致命的弱點,就是當鎖定一個範圍鍵值之後,即使某些不存在的鍵值也會被無辜的鎖定,而造成在鎖定的時候無法插入鎖定鍵值範圍內的任何數據。在某些場景下這可能會對性能造成很大的危害。

在這裏插入圖片描述
Innodb存儲引擎由於實現了行級鎖定,雖然在鎖定機制的實現方面所帶來的性能損耗可能比表級鎖定會要更高一些,但是在整體併發處理能力方面要遠遠優於MyISAM的表級鎖定的。當系統併發量較高的時候,Innodb的整體性能和MyISAM相比就會有比較明顯的優勢了。

但是,Innodb的行級鎖定同樣也有其脆弱的一面,當我們使用不當的時候,可能會讓Innodb的整體性能表現不僅不能比MyISAM高,甚至可能會更差。

  • 如何分析行鎖定?
    通過檢查InnoDB_row_lock狀態變量來分析系統上的行鎖的爭奪情況
    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:系統啓動後到現在總共等待的次數;
對於這5個狀態變量,比較重要的主要是
Innodb_row_lock_time_avg(等待平均時長),
Innodb_row_lock_waits(等待總次數)
Innodb_row_lock_time(等待總時長)這三項。
尤其是當等待次數很高,而且每次等待時長也不小的時候,我們就需要分析系統中爲什麼會有如此多的等待,然後根據分析結果着手指定優化計劃。
最後可以通過 SELECT * FROM information_schema.INNODB_TRX\G;來查詢正在被鎖阻塞的sql語句。

  • 優化建議
    儘可能讓所有數據檢索都通過索引來完成,避免無索引行鎖升級爲表鎖;
    儘可能較少檢索條件,避免間隙鎖;
    儘量控制事務大小,減少鎖定資源量和時間長度;
    鎖住某行後,儘量不要去調別的行或表,趕緊處理被鎖住的行然後釋放掉鎖;
    涉及相同表的事務,對於調用表的順序儘量保持一致;
    在業務環境允許的情況下,儘可能低級別事務隔離。

五. 葉鎖
開銷和加鎖時間界於表鎖和行鎖之間;
會出現死鎖;
鎖定粒度界於表鎖和行鎖之間,併發度一般。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章