相信不少朋友在面試的時候,都會被問到樂觀鎖和悲觀鎖的問題,若是不清楚其概念和用法的狀況下,相信不少朋友都會感受很懵逼,那麼面試的結果也就不言而喻了。
那麼樂觀鎖和悲觀鎖究竟是個什麼東西,用它能來作什麼呢?
相信你們都遇到這種場景,當不少人(一兩我的估計不行)同時對同一條數據作修改的時候,那麼數據的最終結果是怎樣的呢?
這也就是咱們說的併發狀況,這樣會致使如下兩種結果:html
這樣的問題怎麼解決呢?因而乎,鎖就這樣產生了,鎖分爲樂觀鎖和悲觀鎖,它的目的是用來解決併發控制的問題。
MyISAM引擎不支持事務,因此不考慮它有樂觀鎖和悲觀鎖概念。MyISAM只有表鎖,鎖又分爲讀鎖和寫鎖。在這裏咱們只討論InnoDB引擎。
須要注意的是,樂觀鎖和悲觀鎖並非解決併發控制的惟一手段(也可使用消息中間件kafka,MQ之類的做爲緩衝等等),並且樂觀鎖和悲觀鎖並不只限制在mysql中使用,它是一種概念,不少其餘的應用,如redis,memcached等,只要存在併發狀況的,均可以應用這種概念,只是方式上有些差異而已。mysql
1、樂觀鎖
樂觀鎖,簡單地說,就是從應用系統層面上作併發控制,去加鎖。
實現樂觀鎖常見的方式:版本號version
實現方式,在數據表中增長版本號字段,每次對一條數據作更新以前,先查出該條數據的版本號,每次更新數據都會對版本號進行更新。在更新時,把以前查出的版本號跟庫中數據的版本號進行比對,若是相同,則說明該條數據沒有被修改過,執行更新。若是比對的結果是不一致的,則說明該條數據已經被其餘人修改過了,則不更新,客戶端進行相應的操做提醒。
使用版本號實現樂觀鎖
使用版本號時,能夠在數據初始化時指定一個版本號,每次對數據的更新操做都對版本號執行+1操做。並判斷當前版本號是否是該數據的最新的版本號。 面試
//1.查詢出商品信息 redis
select status,version from t_goods where id=#{id} sql
//2.根據商品信息生成訂單 數據庫
//3.修改商品status爲2 session
update t_goods 併發
set status=2,version=version+1 memcached
where id=#{id} and version=#{version};性能
注意第二個事務執行update時,第一個事務已經提交了,因此第二個事務可以讀取到第一個事務修改的version。
下面這種極端的狀況:
咱們知道MySQL數據庫引擎InnoDB,事務的隔離級別是Repeatable Read,所以是不會出現髒讀、不可重複讀。
在這種極端狀況下,第二個事務的update因爲不能讀取第一個事務未提交的數據(第一個事務已經對這一條數據加了排他鎖,第二個事務須要等待獲取鎖),第二個事務獲取了排他鎖後,會發現version已經發生了改變從而提交失敗。
2、悲觀鎖
悲觀鎖,簡單地說,就是從數據庫層面上作併發控制,去加鎖。
悲觀鎖的實現方式有兩種:共享鎖(讀鎖)和排它鎖(寫鎖)
共享鎖(IS鎖),實現方式是在sql後加LOCK IN SHARE MODE,好比SELECT ... LOCK IN SHARE MODE,即在符合條件的rows上都加了共享鎖,這樣的話,其餘session能夠讀取這些記錄,也能夠繼續添加IS鎖,可是沒法修改這些記錄直到你這個加鎖的session執行完成(不然直接鎖等待超時)。
排它鎖(IX鎖),實現方式是在sql後加FOR UPDATE,好比SELECT ... FOR UPDATE ,即在符合條件的rows上都加了排它鎖,其餘session也就沒法在這些記錄上添加任何的S鎖或X鎖。若是不存在一致性非鎖定讀的話,那麼其餘session是沒法讀取和修改這些記錄的,可是innodb有非鎖定讀(快照讀並不須要加鎖),for update以後並不會阻塞其餘session的快照讀取操做,除了select ...lock in share mode和select ... for update這種顯示加鎖的查詢操做。
經過對比,發現for update的加鎖方式無非是比lock in share mode的方式多阻塞了select...lock in share mode的查詢方式,並不會阻塞快照讀。
mysql InnoDB引擎默認的修改數據語句:update,delete,insert都會自動給涉及到的數據加上排他鎖,select語句默認不會加任何鎖類型。
在Java中,synchronized的思想也是悲觀鎖。
以排它鎖爲例
要使用悲觀鎖,咱們必須關閉mysql數據庫的自動提交屬性,由於MySQL默認使用auto commit模式,也就是說,當你執行一個更新操做後,MySQL會馬上將結果進行提交。set autocommit=0;
//0.開始事務
begin;/begin work;/start transaction; (三者選一就能夠)
//1.查詢出商品信息
select status from t_goods where id=1 for update;
//2.根據商品信息生成訂單
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status爲2
update t_goods set status=2;
//4.提交事務
commit;/commit work;
上面的查詢語句中,咱們使用了select…for update的方式, 這樣就經過開啓排他鎖的方式實現了悲觀鎖。此時在t_goods表中,id爲1的 那條數據就被咱們鎖定了,其它的事務必須等本次事務提交以後才能執行。這樣咱們能夠保證當前的數據不會被其它事務修改。
補充:
1.MyISAM在執行查詢語句(SELECT)前,會自動給涉及的全部表加讀鎖,在執行更新操做 (UPDATE、DELETE、INSERT等)前,會自動給涉及的表加寫鎖。
2.MySQL InnoDB默認行級鎖。 行級鎖都是基於索引的,若是一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖把整張表鎖住。
3.從上面對兩種鎖的介紹,咱們知道兩種鎖各有優缺點,不可認爲一種好於另外一種,像樂觀鎖適用於寫比較少的狀況下(多讀場景),即衝突真的不多發生的時候,這樣能夠省去了鎖的開銷,加大了系統的整個吞吐量。但若是是多寫的狀況,通常會常常產生衝突,這就會致使上層應用會不斷的進行retry,這樣反卻是下降了性能,因此通常多寫的場景下用悲觀鎖就比較合適。
參考:http://www.cnblogs.com/exceptioneye/p/5373477.html