鎖是併發控制中最核心的概念之一,在MySQL中的鎖分兩大類,一種是讀鎖,一種是寫鎖,讀鎖也能夠稱爲共享鎖(shared lock),寫鎖也一般稱爲排它鎖(exclusive lock)。php
這裏先不討論鎖的具體實現,描述一下鎖的概念:讀鎖是共享的,或者說是相互不阻塞的。多個客戶在同一時刻能夠同時讀取一個資源,且互不干擾。寫鎖則是排他的,就是說一個寫鎖會阻塞其餘的寫鎖和讀鎖,這是出於安全策略的考慮,只有這樣,才能確保在給定時間裏,只有一個用戶能執行寫入,並防止其餘用戶讀取正在寫入的同一資源。另外在通常狀況下,寫鎖比讀鎖優先級高。html
MySQL中的鎖有兩種粒度,一種是表鎖,在表級別加鎖,是MySQL中最基本的鎖策略,而且開銷最小,這種鎖的併發性能較低;另外一種爲行鎖,在行級加鎖,併發性較高。表鎖與行鎖沒有絕對的性能強弱之分,在應用中能夠根據實際場景選擇,在鎖粒度與數據安全之間尋求一種平衡機制。mysql
InnoDB的行鎖是基於索引實現的,若是不經過索引訪問數據,InnoDB會使用表鎖。sql
鎖的具體實現協議大致分爲兩種:顯式鎖和隱式鎖。顯式鎖是指根據用戶須要手動去請求的鎖。隱式鎖則是指存儲引擎自行根據須要施加的鎖。顯式鎖的用法示例:數據庫
例1:開啓兩個ssh鏈接同一主機,進入MySQL,在鏈接A上對錶tbl2作讀鎖操做:緩存
1 mysql> USE mysql; 2 mysql> LOCK TABLE tbl2 READ;
在鏈接B上讀取數據是能夠的,可是寫入數據不行:安全
1 mysql> USE mysql; 2 mysql> SELECT * FROM tbl2; 3 mysql> INSERT INTO tbl2 VALUES (1,'tom'); #會一直卡在這一步,不向後執行。
當在鏈接1上將tbl2解鎖後,就能寫入數據了:session
在php與數據庫的交互中,若是併發量大,而且都去進行數據庫的修改的話,就有一個問題須要注意.數據的鎖問題.就會牽扯數據庫的事務跟隔離機制
數據庫事務依照不一樣的事務隔離級別來保證事務的ACID特性,也就是說事務不是一開啓就能解決全部併發問題。一般狀況下,這裏的併發操做可能帶來四種問題:併發
事務的隔離級別有四種:oracle
1.READ UNCOMMITTED(未提交讀)
在READ UNCOMMITTED級別,事務中的修改即便沒有提交,對其它事務也都是可見的。即事務可讀取未提交的數據,這稱爲髒讀(Dirty Read)。這會致使不少問題,在實際應用中通常不多用到。
2.READ COMMITED(提交讀)
READ COMMITTED表示只能讀取事務修改提交後的數據。此級別有時候也叫作不可重複讀(nonrepeatable read),由於兩次執行一樣的查詢,可能會獲得不同的結果。
3.REPEATABLE READ(可重複讀)
此級別解決了髒讀的問題,保證了在同一個事務中屢次一樣記錄的結果是一致的。但會帶來新的問題——幻讀(Phantom Read)。MySQL默認使用此級別。
4.SERIALIZABLE(可串行化)
SERIALIZABLE是最高的隔離級別。它會強制事務串行執行,避免了幻讀、髒讀的問題,可是犧牲了併發性。
示例:
mysql> SELECT @@session.tx_isolation; #查看當前事務隔離級別 +------------------------+ | @@session.tx_isolation | +------------------------+ | REPEATABLE-READ | +------------------------+ 1 row in set (0.00 sec)
mysql> SET @@session.tx_isolation='級別'; 可設置隔離級別
樂觀鎖和悲觀鎖是一種機制不是指具體的鎖。
悲觀鎖(Pessimistic Lock),顧名思義,就是很悲觀,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。假定會發生併發衝突,屏蔽一切可能違反數據完整性的操做。
樂觀鎖(Optimistic Lock),顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,樂觀鎖適用於讀多寫少的應用場景,這樣能夠提升併發粒度。
悲觀鎖實現:事務隔離級別的可串行化就是典型的悲觀鎖機制,讀加讀鎖,寫加寫鎖。這是基於鎖的併發控制。
樂觀鎖實現:innoDB引擎的樂觀鎖機制是經過MVCC實現的。叫多版本併發控制(讀不加鎖讀寫不衝突),是經過保存數據在某一個時間點的快照來實現的。所以每個事務不管執行多長時間看到的數據,都是同樣的。
增刪改操做都屬於當前讀,不會讀取歷史版本的快照數據。
普通的select屬於快照讀,讀取的是快照數據也多是歷史數據。
手動加鎖的select是當前讀 例如:select * from tb for update或者select * from tb lock in share mode
涉及搶購、秒殺、抽獎、搶票等活動時,爲了不超賣,那麼庫存數量是有限的,可是若是同時下單人數超過了庫存數量,就會致使商品超賣問題。那麼咱們怎麼來解決這個問題呢,個人思路以下(僞代碼):
sql1:查詢商品庫存
if(庫存數量 > 0)
{
//生成訂單...
sql2:同時庫存-1
}
當沒有併發時,上面的流程看起來是再正常不過了,假設同時兩我的下單,而庫存只有1個了,在sql1階段兩我的查詢到的庫存都是>0的,因而最終都執行了sql2,庫存最後變爲-1,超售了,這不是咱們想要的結果吧。
解決這個問題比較流行的思路我總結了下:
1.用額外的單進程處理一個隊列,下單請求放到隊列裏,一個個處理,就不會有併發的問題了,可是要額外的開啓後臺進程以及延遲問題,這裏暫不予考慮。這裏我可以使用消息隊列,咱們經常使用到Memcacheq、Radis。 好比:有100張票可供用戶搶,那麼就能夠把這100張票放到緩存中,讀寫時不要加鎖。 當併發量大的時候,可能有500人左右搶票成功,這樣對於500後面的請求能夠直接轉到活動結束的靜態頁面。進去的500我的中有400我的是不可能得到商品的。因此能夠根據進入隊列的前後順序只能前100我的購買成功。後面400我的就直接轉到活動結束頁面。固然進去500我的只是舉個例子,至於多少能夠本身調整。而活動結束頁面必定要用靜態頁面,不要用數據庫。這樣就減輕了數據庫的壓力。
2.mysql樂觀鎖,意思是好比總庫存是2,搶購事件提交時,立馬將庫存+1,那麼此時庫存是3,而後訂單生成後,在更新庫存前再查詢一次庫存(由於訂單生成理所固然庫存-1,可是先不急,再查一次庫存返回結果是3),看看跟預期的庫存數量(這裏預期的庫存是3)是否保持一致,不一致就回滾,提示用戶庫存不足。這裏說道悲觀鎖,可能有朋友會問,那必定有樂觀鎖了吧??這裏我就淺談下我所瞭解的悲觀與樂觀鎖了
悲觀鎖的特色是先獲取鎖,再進行業務操做,即「悲觀」的認爲獲取鎖是很是有可能失敗的,所以要先確保獲取鎖成功再進行業務操做。一般所說的「一鎖二查三更新」即指的是使用悲觀鎖。
一般來說在數據庫上的悲觀鎖須要數據庫自己提供支持,即經過經常使用的select … for update操做來實現悲觀鎖。當數據庫執行select for update時會獲取被select中的數據行的行鎖,所以其餘併發執行的select for update若是試圖選中同一行則會發生排斥(須要等待行鎖被釋放),所以達到鎖的效果。
select for update獲取的行鎖會在當前事務結束時自動釋放,所以必須在事務中使用。
這裏須要注意的一點是不一樣的數據庫對select for update的實現和支持都是有所區別的,例如oracle支持select for update no wait,表示若是拿不到鎖馬上報錯,而不是等待,mysql就沒有no wait這個選項。另外mysql還有個問題是select for update語句執行中全部掃描過的行都會被鎖上,這一點很容易形成問題。
所以若是在mysql中用悲觀鎖務必要肯定走了索引,而不是全表掃描。
在每次去拿數據的時候認爲別人不會修改,不對數據上鎖,可是在提交更新的時候會判斷在此期間數據是否被更改,若是被更改則提交失敗。
樂觀鎖實現:使用版本控制字段,再利用行鎖的特性實現樂觀鎖
有一張訂單表order,有字段id、order_no、 price, 爲實現樂觀鎖控制,添加version字段,默認值爲0
id | 1 |
order_no | 123456 |
price | 5 |
version | 0 |
假設兩我的同時進來修改該條數據,操做爲:
1. 先查詢該數據 select * from order where id = 1
2. 修改該條數據 update order set price = 1 where id = 1 and price = 5
若是兩我的同時查詢到該條數據price = 5, 能夠執行update操做, 但任意一方還沒執行update操做,那麼最後雙方都執行update,致使數據被修改兩次,這樣很容易產生錯誤數據(髒數據)
使用version字段控制版本後:
1. 兩人先查詢該數據 select * from order where id = 1
此時兩人查詢到的數據同樣,id = 1, price = 5, order_no = 123456, version = 0
2. 兩人都發現該條數據price = 5, 符合update條件,第一人執行update(由於mysql行鎖的特性,兩人不可能同時修改一條數據,因此update同一條數據的時候,是有前後順序的,只有在第一個執行完update,才能釋放行鎖,第二個繼續進行update):
update order set price = 1, version = version + 1 where id = 1 and price = 5 and version = 0
執行完成後,version字段值將變成1, 第二人執行update:
update order set price = 1, version = version + 1 where id = 1 and price = 5 and version = 0
此時的version的值已經被修改成1,因此第二人修改失敗,實現樂觀鎖控制。