這有一把鑰匙,打開mysql的鎖

前言

鎖是計算機協調多個進程或線程併發訪問某一資源的控制機制。在數據庫中,除了計算資源(CPU、I/O)的爭用以外,數據也是一種多用戶共享的資源。如何保證數據併發訪問的一致性和有效性是全部數據庫必須解決的一個問題。相對於其餘數據庫而言,mysql的鎖機制比較簡單,最顯著的特色是不一樣的存儲引擎支持不一樣的鎖機制。好比MyISAM和MEMORY採用表級鎖;BDB存儲引擎採用頁面鎖,但也支持表級鎖;InnoDB存儲引擎採用行級鎖,也支持表級鎖,默認狀況使用行級鎖。三種鎖的特色以下:mysql

  • 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定力度大,發生鎖衝突的機率最高,併發度最低。
  • 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度小,發生鎖衝突的機率最低,併發度也高。
  • 頁面鎖:開銷和加鎖時間介於表鎖和行鎖之間;會出現死鎖;鎖粒度介於表鎖和行鎖之間,併發度通常。

表鎖

Mysql表鎖有兩種模式:表共享讀鎖(table read lock)和表獨佔寫鎖(table write lock)。對錶的讀操做不會阻塞其餘用戶對同一表的讀操做,但會阻塞對同一表的寫請求;對錶的寫操做,會阻塞其餘用戶對同一表的讀和寫操做。
準備數據:sql

CREATE TABLE `test` (
  `id` bigint(20) NOT NULL auto_increment,
  `name` varchar(100) NOT NULL,
  `age` int(10) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='測試表';
複製代碼

表獨佔寫鎖

一、用戶1:lock table test write; 給test表加獨佔寫鎖 二、當前用戶1對test表的read,update和insert均可以執行 數據庫

用戶2對test表的查詢被阻塞,須要等待用戶1對test表的寫鎖釋放

三、用戶1釋放test表的寫鎖

用戶2馬上查詢到結果

表共享讀鎖

一、用戶1給test表加共享讀鎖bash

二、當前用戶1能夠查詢test表的數據,用戶2也能夠查詢test表的數據

三、用戶1對test表進行更新,插入報錯,用戶2對test表的更新和插入會等待鎖

用戶1更新報錯: 併發

用戶2更新會等待鎖釋放:
四、用戶1查詢其餘表也會報錯,用戶2能夠查詢其餘表

用戶1查詢其餘表報錯: oracle

五、用戶1釋放表讀鎖,用戶2等到了鎖釋放,更新完成

查詢表級鎖競爭狀況

能夠經過檢查table_locks_waited和table_locks_immediate狀態變量來分析系統上的表鎖定爭奪:若是table_lock_waited的值比較高,則說明存在較嚴重的表級鎖爭用狀況。測試

併發插入

上面提到MyISAM表的讀和寫是串行的,但這是就整體而言。在必定條件下,MyISAM也支持查詢和插入的併發執行。MyISAM存儲引擎有一個系統變量concurrent_insert,專門用來控制併發插入的行爲,能夠取值0,1,2;優化

  • 當concurrent_insert=0,不容許併發插入。
  • 當concurrent_insert=1,若是MyISAM表中沒有空洞(即表的中間沒有被刪除的行),容許一個進程在查詢數據,另外一個進程在表尾插入記錄,這也是mysql的默認設置。
  • 當concurrent_insert=2,不管MyISAM表有沒有空洞,均可以在表尾插入記錄。

舉例:
一、用戶1對test表加共享讀鎖(注意這裏要用read local)ui

二、用戶2能夠對test表進行插入操做,可是更新會等待鎖釋放

可是即便用戶2插入成功,可是用戶1仍是隻能查到加鎖以前的記錄,查詢不到用戶2新插入的記錄spa

InnoDb行鎖

兩種行鎖

  • 共享鎖(s):又稱讀鎖。容許一個事務去讀一行,阻止其餘事務得到相同數據集的排它鎖。若事務1對記錄A加上S鎖,則事務1能夠讀取A但不能修改A,其餘事務只能再對A加S鎖,不能加X鎖。這就保證了其餘事務能夠讀A,但在事務1釋放A上的S鎖以前不能對A作任何的修改。
  • 排他鎖(X):又稱寫鎖。獲取排他鎖的事務能夠更新數據,阻止其餘事務獲取相同數據集的共享讀鎖和排他寫鎖。若事務1對記錄A加了X鎖,則事務1能夠讀A,也能夠修改A,其餘事務不能對A加任何鎖,知道事務1釋放A上的鎖。
    注意:對於共享鎖很好理解,就是多個事務只能讀取數據不能修改數據。可是排他鎖容易錯誤地理解成:若是一個事務鎖住一行數據後,其餘事務不能讀物和修改該行數據。其實排他鎖指的是一個事務對一條記錄加上排他鎖,其餘事務不能對該記錄加其餘的鎖。innoDb引擎默認的update,delete和insert會自動給涉及的數據加上排他鎖,select語句默認不會加任何鎖。因此加過排他鎖的數據行在其餘事務中不能修改數據,也不能經過for update加排他鎖或者lock in share mode加共享鎖,可是能夠直接經過select...from...的方式查詢數據,由於普通的查詢沒有任何鎖機制。
    事務能夠經過如下語句顯式給記錄集加共享鎖或排他鎖:
  • 共享鎖(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。
  • 排他鎖(X):SELECT * FROM table_name WHERE ... FOR UPDATE。

行鎖實現方式

InnoDb行鎖是經過給索引上的索引項加鎖來實現的,這一點mysql與oracle不一樣,oracle是經過在數據塊中對相應數據行加鎖來實現的。InnoDb的這種行鎖實現特色意味着:只有經過索引條件檢索數據,InnoDb纔會使用行級鎖,不然InnoDb將使用表鎖。 舉個例子:

  • 在不經過索引條件查詢的時候,InnoDb使用的是表鎖,而不是行鎖。
create table test_no_index
(
	id bigint(20) not null,
	name varchar(50) not null 
) ENGINE = INNODB COMMENT ='測試innoDb的行鎖實現方式';

insert into test_no_index(id,name) values(1,'1');
insert into test_no_index(id,name) values(2,'2');
複製代碼

一、當用戶1查詢id=1的記錄並對這行記錄加上排他鎖(要把自動提交關掉)

二、這時用戶2查詢id=2的記錄卻須要等待。緣由是在沒有索引的狀況下,InnoDb只能使用表鎖。

三、當咱們給這個表的id字段加上一個索引後,再重複執行第1,2步驟

alter table test_no_index add index id(id);
複製代碼

用戶1成功鎖定id=1的記錄

用戶2也能夠成功查詢並鎖定id=2的記錄

間隙鎖(Next-Key鎖)

當咱們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDb會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但不存在的記錄,叫作間隙(GAP),InnoDb也會對這個「間隙」加鎖,這種機制就是所謂的「間隙鎖」。
例如:假如emp表中有101條數據,id的值分別是1,2,...,100,101,下面的sql:

select * from emp where id>100 for update;
複製代碼

是一個範圍條件的檢索,InnnoDb不只會對符合條件的id=101的記錄加鎖,也會對id>101的間隙加鎖。

InnoDb使用間隙鎖的目的,一方面是爲了防止幻讀,以知足隔離級別的要求。對於上面的例子,要是不使用間隙鎖,若是其餘事務插入了id>100的任何記錄,那麼本事務若是再次執行上述語句,就會產生幻讀。另外一方面,是爲了知足其恢復和複製的須要。
顯然,在使用範圍條件檢索並鎖定記錄時,InnoDb這種鎖機制會阻塞符合條件範圍內鍵值的併發插入,這每每會形成嚴重的鎖等待。所以,在實際應用開發中,尤爲是併發插入比較多的應用,要儘可能優化業務邏輯,儘可能使用相等來訪問和更新數據,儘可能避免使用範圍條件。

悲觀鎖和樂觀鎖

首先,悲觀鎖和樂觀鎖都是一種思想,並非真實存在於數據中的一種機制。

悲觀鎖

當認爲數據被併發修改的概率比較大,須要在修改以前藉助於數據庫的鎖機制對數據進行加鎖的思想稱爲悲觀鎖,又稱PCC(Pessimisic Concurrency Control)。在效率方面,處理鎖的操做會產生額外的開銷,並且增長了死鎖的機會。當一個線程在處理某行數據的時候,其它線程只能等待。 ** 實現方式 ** 悲觀鎖的實現依賴數據庫的鎖機制,流程以下:

  • 修改記錄前,對記錄加上排他鎖。
  • 若是加鎖失敗,說明這條記錄正在被修改,那麼當前查詢要等待會拋出異常。
  • 若是加鎖成功,能夠對這條記錄進行修改,事務完成後進行解鎖。
  • 加鎖修改期間,其餘事務想要對這條記錄進行操做,都要等待鎖釋放或者不想等待拋出異常。 注意:在使用innoDb引擎實現悲觀鎖時,必須關閉自動提交。舉例:
set autocommit=0;
//事務開始
begin;
//查詢商品在某個倉庫的庫存
select * from inventory_summary where sku_id=1000 and warehouse_id=123456 for update;
//修改商品庫存爲2
update inventory_summary set quantity=2 where sku_id=1000 and warehouse_id=123456;
//提交事務
commit;
複製代碼

樂觀鎖

樂觀鎖的實現不須要藉助數據庫的鎖機制,只要兩個步驟:衝突檢測(比較)和數據更新,其中一種典型的實現方法就是CAS(比較交換,Compare And Swap)。CAS這裏不作詳細解釋,就是先比較後更新,在對一個數據進行更新前,先持有這個數據原有值得備份,若是當前更新的值和原來的備份相等才進行更新,不然判斷爲數據被其餘線程改過了。當前線程再次進行重試。 *** ABA問題 ***

//查詢出商品的庫存是3,而後用庫存爲3做爲條件進行更新
select quantity from inventory_summary where sku_id=1000 and warehouse_id=123456;
//修改庫存爲2
update inventory_summary set quantity=2 where sku_id=1000 and warehouse_id=123456 and quantity=3;
複製代碼

在更新以前,先查詢原有的庫存數,在更新庫存時,用原有的庫存數做爲修改條件。相等則更新,不然認爲是被改過的數據。可是會存在這樣的狀況下,好比線程A取出庫存數3,線程B先將庫存數更新爲2,又將庫存數更新爲3,而後線程A更新的時候發現庫存仍然是3,而後更新成功。可是這個過程可能存在問題。** 解決ABA問題一個方法是經過一個順序遞增的version字段或者時間戳**

//查詢商品庫存表,version=1
select version from inventory_summary where sku_id=1000 and warehouse_id=123456;

//修改商品庫存爲2
update inventory_summary set quantity=2,version=version+1 where sku_id=1000 and warehouse_id=123456 and version=1;
複製代碼

在每次更新的時候都帶上一個版本號,一旦版本號和數據版本號一致就能夠執行修改並對版本號執行+1操做,不然執行失敗。由於每次操做版本號遞增,因此不會出現ABA問題。還可使用時間戳,由於時間戳具備自然的順序遞增性。

樂觀鎖和悲觀鎖比較

樂觀鎖並非真正的加鎖,優勢是效率高,缺點是更新時間的機率比較高(尤爲是併發度比較高的環境中);悲觀鎖依賴於數據庫鎖機制,更新失敗的機率低,可是效率也低。

相關文章
相關標籤/搜索