每次拿數據的時候都認爲別的線程會修改數據,因此每次拿數據的時候都會給數據上鎖。上鎖以後,當別的線程想要拿數據時,就會阻塞。直到給數據上鎖的線程將事務提交或者回滾。傳統的關係數據庫裏面不少用了這種鎖機制,好比行鎖,表鎖,共享鎖,排他鎖等,都是在作操做以前先上鎖。mysql
下面的圖從網上粘的,用mysql的兩個視窗演示一下行鎖(左邊先執行)sql
(1) 左邊的線程,在事務中經過select for update語句給sid=1的數據行上了鎖,右邊的線程此時可使用select語句讀取數據,可是若是也使用select for update語句就會阻塞,使用update, add, delete也會阻塞。 而當左邊的線程將事務提交(或者回滾),右邊的線程就會獲取鎖,線程再也不阻塞數據庫
(2) 此時右邊的線程獲取鎖,左邊的線程執行此類操做,也會被阻塞。安全
(3) 固然,select都不行,update等寫操做也要阻塞等待。for update是排他鎖併發
行級鎖是mysql中鎖定粒度最細的一種鎖。表示只針對當前操做的行進行加鎖。行級鎖能大大減小數據庫操做的衝突,其加鎖粒度最小,但加鎖的開銷也最大。行級鎖分爲共享鎖和排他鎖oracle
開銷大,加鎖慢,會出現死鎖。發生鎖衝突的機率最低,併發度也最高。性能
表級鎖是mysql中鎖定粒度最大的一種鎖,表示對當前操做的整張表加鎖,它實現簡單,資源消耗較少,被大部分mysql引擎支持。最常使用的MyISAM與InnoDB都支持表級鎖定。表級鎖定分爲表共享讀鎖(共享鎖)與表獨佔寫鎖(排他鎖)spa
開銷小,加鎖快,不會出現死鎖。發生鎖衝突的機率最高,併發度也最低。.net
頁級鎖是 MySQL 中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但衝突多,行級衝突少,但速度慢。所以,採起了折衷的頁級鎖,一次鎖定相鄰的一組記錄。BDB 支持頁級鎖。線程
開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度通常。
共享鎖又稱讀鎖,是讀取操做建立的鎖。其餘用戶能夠併發讀取數據,但任何事務都不能對數據進行修改(獲取數據上的排他鎖),直到已釋放全部共享鎖。
若是事務T
對數據A
加上共享鎖後,則其餘事務只能對A
再加共享鎖,不能加排他鎖。獲准共享鎖的事務只能讀數據,不能修改數據。
用法
SELECT ... LOCK IN SHARE MODE;
在查詢語句後面增長LOCK IN SHARE MODE
,MySQL 就會對查詢結果中的每行都加共享鎖,當沒有其餘線程對查詢結果集中的任何一行使用排他鎖時,能夠成功申請共享鎖,不然會被阻塞。其餘線程也能夠讀取使用了共享鎖的表,並且這些線程讀取的是同一個版本的數據。
排他鎖又稱寫鎖、獨佔鎖,若是事務T
對數據A
加上排他鎖後,則其餘事務不能再對A
加任何類型的封鎖。獲准排他鎖的事務既能讀數據,又能修改數據。
用法
SELECT ... FOR UPDATE;
在查詢語句後面增長FOR UPDATE
,MySQL 就會對查詢結果中的每行都加排他鎖,當沒有其餘線程對查詢結果集中的任何一行使用排他鎖時,能夠成功申請排他鎖,不然會被阻塞。
意向鎖是表級鎖,其設計目的主要是爲了在一個事務中揭示下一行將要被請求鎖的類型。InnoDB 中的兩個表鎖:
IS
鎖;IX
鎖。意向鎖是 InnoDB 自動加的,不須要用戶干預。
對於INSERT
、UPDATE
和DELETE
,InnoDB 會自動給涉及的數據加排他鎖;對於通常的SELECT
語句,InnoDB 不會加任何鎖,事務能夠經過如下語句顯式加共享鎖或排他鎖。
共享鎖:SELECT ... LOCK IN SHARE MODE;
排他鎖:SELECT ... FOR UPDATE;
假設有一張商品表 goods,它包含 id,商品名稱,庫存量三個字段,表結構以下:
CREATE TABLE `goods` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) DEFAULT NULL, `stock` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_name` (`name`) USING HASH ) ENGINE=InnoDB
插入以下數據:
INSERT INTO `goods` VALUES ('1', 'prod11', '1000'); INSERT INTO `goods` VALUES ('2', 'prod12', '1000'); INSERT INTO `goods` VALUES ('3', 'prod13', '1000'); INSERT INTO `goods` VALUES ('4', 'prod14', '1000'); ...
假設有A、B兩個用戶同時各購買一件 id=1 的商品,用戶A獲取到的庫存量爲 1000,用戶B獲取到的庫存量也爲 1000,用戶A完成購買後修改該商品的庫存量爲 999,用戶B完成購買後修改該商品的庫存量爲 999,此時庫存量數據產生了不一致。
有兩種解決方案:
(1) 悲觀鎖方案:每次獲取商品時,對該商品加排他鎖。也就是在用戶A獲取獲取 id=1 的商品信息時對該行記錄加鎖,期間其餘用戶阻塞等待訪問該記錄。悲觀鎖適合寫入頻繁的場景。
begin; select * from goods where id = 1 for update; update goods set stock = stock - 1 where id = 1; commit;
(2) 樂觀鎖方案:每次獲取商品時,不對該商品加鎖。在更新數據的時候須要比較程序中的庫存量與數據庫中的庫存量是否相等,若是相等則進行更新,反之程序從新獲取庫存量,再次進行比較,直到兩個庫存量的數值相等才進行數據更新。樂觀鎖適合讀取頻繁的場景。
#不加鎖獲取 id=1 的商品對象 select * from goods where id = 1 begin; #更新 stock 值,這裏須要注意 where 條件 「stock = cur_stock」,只有程序中獲取到的庫存量與數據庫中的庫存量相等才執行更新 update goods set stock = stock - 1 where id = 1 and stock = cur_stock; commit;
若是咱們須要設計一個商城系統,該選擇以上的哪一種方案呢?
查詢商品的頻率比下單支付的頻次高,基於以上可能會優先考慮第二種方案(這裏只考慮以上兩種方案的狀況下)。
因爲InnoDB預設是Row-Level Lock,因此只有「明確」的指定主鍵,MySQL纔會執行Row lock ,不然MySQL將會執行Table Lock.
一、只根據主鍵進行查詢,而且查詢到數據,主鍵字段產生行鎖。
begin; select * from goods where id = 1 for update; commit;
二、只根據主鍵進行查詢,沒有查詢到數據,不產生鎖。
begin; select * from goods where id = 1 for update; commit;
三、根據主鍵、非主鍵索引(name)進行查詢,而且查詢到數據,主鍵字段產生行鎖,name字段產生行鎖。
begin; select * from goods where id = 1 and name='prod11' for update; commit;
四、根據主鍵、非主鍵含索引(name)進行查詢,沒有查詢到數據,不產生鎖。
begin; select * from goods where id = 1 and name='prod12' for update; commit;
五、根據非索引(stock)進行查詢,而且查詢到數據,stock字段產生表鎖。
begin; select * from goods where stock='1000' for update; commit;
六、根據非索引(stock)進行查詢,沒有查詢到數據,stock字段產生表鎖。
begin; select * from goods where stock='9999' for update; commit;
7. 只根據主鍵進行查詢,查詢條件爲模糊(不等於, like等),不管是否查詢到數據,主鍵字段產生表鎖。
begin; select * from goods where id <> 1 for update; commit;
InnoDb行鎖是經過給索引上的索引項加鎖來實現的,這一點mysql與oracle不一樣,後者是經過在數據塊中對相應數據行加鎖來實現的。InnoDB這種行鎖實現的特色意味着: 只有經過索引條件檢索數據,InnoDB才使用行級鎖,不然,InnoDB將使用表鎖。
在實際應用中,要特別注意 InnoDB 行鎖的這一特性,否則的話,可能致使大量的鎖衝突,從而影響併發性能。
mysql innodb的間隙鎖定(next-key locking)是爲了防止幻讀。當mysql的隔離級別爲repeatable read時候會觸發間隙鎖定。
next-key的具體工做方式是:
(1) 選擇一個不存在的行,則鎖住全部的insert行爲
(2) 用範圍select,如select * from dual where id > 100, 會鎖住全部id > 100的insert行爲
InnoDB對索引記錄的鎖定也影響索引記錄以前的「間隙gap」 。若是一個用戶對索引記錄R加了一個共享或排他鎖定,那其餘用戶將不能在R以前當即插入新的記錄。這種間隙鎖定用於防止所謂的「phantom problem」。假設需讀取和鎖定表 CHILD
中標識符大於 100 的子行,並更新所搜索到的記錄中某些字段。
SELECT * FROM CHILD WHERE ID > 100 FOR UPDATE;
假設表 CHILD
中有一個索引字段 ID
。咱們的查詢將從 ID
大於100的第一條記錄開始掃描索引記錄。 如今,假設加在索引記錄上的鎖定不能阻止在間隙處的插入,一個新的子記錄將可能在事務處理中被插入到表中。 若是如今在事務中再次執行
SELECT * FROM CHILD WHERE ID > 100 FOR UPDATE;
在查詢返回的記錄集中將會有一個新的子記錄。這與事務的隔離原則是相反的:一個事務應該可以運行,以便它已經讀的數據在事務過程當中不改變。若是咱們把一套行視爲數據項,新的「幽靈」子記錄可能會違反這一隔離原則。
當InnoDB掃描一個索引之時,它也鎖定因此記錄中最後一個記錄以後的間隙。剛在前一個例子中發生:InnoDB設置的鎖定防止任何插入到id可能大過100的表。
你能夠用next-key鎖定在你的應用程序上實現一個惟一性檢查:若是你以共享模式讀數據,而且沒有看到你將要插入的行的重複,則你能夠安全地插入你的行,而且知道在讀過程當中對你的行的繼承者設置的next-key鎖定與此同時阻止任何人對你的行插入一個重複。所以,the next-key鎖定容許你鎖住在你的表中並不存在的一些東西。
gap間隙鎖具體鎖定的範圍參看一下這篇 http://blog.itpub.net/30221425/viewspace-1787312/ 個人理解是索引數據的先後間隙, 假設索引數據是m,範圍是 [mPre, mNext)
MyISAM 中是不會產生死鎖的,由於 MyISAM 老是一次性得到所需的所有鎖,要麼所有知足,要麼所有等待。而在 InnoDB 中,鎖是逐步得到的,就形成了死鎖的可能。
在 MySQL 中,行級鎖並非直接鎖記錄,而是鎖索引。索引分爲主鍵索引和非主鍵索引兩種,若是一條 SQL 語句操做了主鍵索引,MySQL 就會鎖定這條主鍵索引;若是一條 SQL 語句操做了非主鍵索引,MySQL 就會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。 在進行UPDATE
、DELETE
操做時,MySQL 不只鎖定WHERE
條件掃描過的全部索引記錄,並且會鎖定相鄰的鍵值,即所謂的next-key locking
.
當兩個事務同時執行,一個鎖住了主鍵索引,在等待其餘相關索引;另外一個鎖定了非主鍵索引,在等待主鍵索引。這樣就會發生死鎖。
發生死鎖後,InnoDB 通常均可以檢測到,並使一個事務釋放鎖回退,另外一個獲取鎖完成事務。
有多種方法能夠避免死鎖,這裏只介紹常見的三種: