鎖是計算機協調多個進程或線程併發訪問某一資源的控制機制。在數據庫中,除了計算資源(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;優化
舉例:
一、用戶1對test表加共享讀鎖(注意這裏要用read local)ui
可是即便用戶2插入成功,可是用戶1仍是隻能查到加鎖以前的記錄,查詢不到用戶2新插入的記錄spa
InnoDb行鎖是經過給索引上的索引項加鎖來實現的,這一點mysql與oracle不一樣,oracle是經過在數據塊中對相應數據行加鎖來實現的。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的記錄當咱們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,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)。在效率方面,處理鎖的操做會產生額外的開銷,並且增長了死鎖的機會。當一個線程在處理某行數據的時候,其它線程只能等待。 ** 實現方式 ** 悲觀鎖的實現依賴數據庫的鎖機制,流程以下:
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問題。還可使用時間戳,由於時間戳具備自然的順序遞增性。
樂觀鎖並非真正的加鎖,優勢是效率高,缺點是更新時間的機率比較高(尤爲是併發度比較高的環境中);悲觀鎖依賴於數據庫鎖機制,更新失敗的機率低,可是效率也低。