Mysql 在5.5以前默認使用 MyISAM 存儲引擎,以後使用 InnoDB 。查看當前存儲引擎:程序員
show variables like '%storage_engine%';
複製代碼
MyISAM 操做數據都是使用的表鎖,你更新一條記錄就要鎖整個表,致使性能較低,併發不高。固然同時它也不會存在死鎖問題。sql
而 InnoDB 與 MyISAM 的最大不一樣有兩點:一是 InnoDB 支持事務;二是 InnoDB 採用了行級鎖。也就是你須要修改哪行,就能夠只鎖定哪行。數據庫
在 Mysql 中,行級鎖並非直接鎖記錄,而是鎖索引。索引分爲主鍵索引和非主鍵索引兩種,若是一條sql 語句操做了主鍵索引,Mysql 就會鎖定這條主鍵索引;若是一條語句操做了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。併發
InnoDB 行鎖是經過給索引項加鎖實現的,若是沒有索引,InnoDB 會經過隱藏的聚簇索引來對記錄加鎖。也就是說:若是不經過索引條件檢索數據,那麼InnoDB將對錶中全部數據加鎖,實際效果跟表鎖同樣。由於沒有了索引,找到某一條記錄就得掃描全表,要掃描全表,就得鎖定表。性能
首先說明:數據庫的增刪改操做默認都會加排他鎖,而查詢不會加任何鎖。spa
對某一資源加共享鎖,自身能夠讀該資源,其餘人也能夠讀該資源(也能夠再繼續加共享鎖,即 共享鎖可多個共存),但沒法修改。要想修改就必須等全部共享鎖都釋放完以後。語法爲:.net
select * from table lock in share mode
複製代碼
對某一資源加排他鎖,自身能夠進行增刪改查,其餘人沒法進行任何操做。語法爲:線程
select * from table for update --增刪改自動加了排他鎖
複製代碼
下面援引例子來進行理解(援自:blog.csdn.net/samjustin1/… )。設計
這裏用T1表明一個數據庫執行請求,T2表明另外一個請求,也能夠理解爲T1爲一個線程,T2 爲另外一個線程。code
例1:
T1:select * from table lock in share mode(假設查詢會花很長時間,下面的例子也都這麼假設)
T2:update table set column1='hello'
複製代碼
分析:
T1運行(並加共享鎖)
T2運行
If T1還沒執行完
T2等......
else鎖被釋放
T2執行
endif
複製代碼
T2 之因此要等,是由於 T2 在執行 update 前,試圖對 table 表加一個排他鎖,而數據庫規定同一資源上不能同時共存共享鎖和排他鎖。因此 T2 必須等 T1 執行完,釋放了共享鎖,才能加上排他鎖,而後才能開始執行 update 語句。
例2:
T1:select * from table lock in share mode
T2:select * from table lock in share mode
複製代碼
分析:
這裏T2不用等待T1執行完,而是能夠立刻執行。T1運行,則 table 被加鎖,好比叫lockAT2運行,再對 table 加一個共享鎖,好比叫lockB兩個鎖是能夠同時存在於同一資源上的(好比同一個表上)。這被稱爲共享鎖與共享鎖兼容。這意味着共享鎖不阻止其它人同時讀資源,但阻止其它人修改資源。
例3:
T1:select * from table lock in share mode
T2:select * from table lock in share mode
T3:update table set column1='hello'
複製代碼
分析:
T2 不用等 T1 運行完就能運行,T3 卻要等 T1 和 T2 都運行完才能運行。由於 T3 必須等 T1 和 T2 的共享鎖所有釋放才能進行加排他鎖而後執行 update 操做。
例4:(死鎖的發生)
T1:begin tran select * from table lock in share modeupdate table set column1='hello'
T2:begin tran select * from table lock in share modeupdate table set column1='world'
複製代碼
假設 T1 和 T2 同時達到 select,T1 對 table 加共享鎖,T2 也對 table 加共享鎖,當 T1 的 select 執行完,準備執行 update 時,根據鎖機制,T1 的共享鎖須要升級到排他鎖才能執行接下來的 update.在升級排他鎖前,必須等 table 上的其它共享鎖(T2)釋放,同理,T2也在等 T1 的共享鎖釋放。因而死鎖產生了。
例5:
T1:begin tran update table set column1='hello' where id=10
T2:begin tran update table set column1='world' where id=20
複製代碼
這種語句雖然最爲常見,不少人以爲它有機會產生死鎖,但實際上要看狀況:
那死鎖怎麼解決呢?一種辦法是,以下:
例6:
T1:begin tran select * from table for updateupdate table set column1='hello'
T2:begin tran select * from table for updateupdate table set column1='world'
複製代碼
這樣,當 T1 的 select 執行時,直接對錶加上了排他鎖,T2 在執行 select 時,就須要等 T1 事務徹底執行完才能執行。排除了死鎖發生。但當第三個 user 過來想執行一個查詢語句時,也由於排他鎖的存在而不得不等待,第四個、第五個 user 也會所以而等待。在大併發狀況下,讓你們等待顯得性能就太友好了。 因此,有些數據庫這裏引入了更新鎖(如Mssql,注意:Mysql不存在更新鎖),以下:
例7:
T1:begin transelect * from table (加更新鎖)update table set column1='hello'
T2:begin transelect * from table (加更新鎖)update table set column1='world'
複製代碼
更新鎖其實就能夠當作排他鎖的一種變形,只是它也容許其餘人讀(而且還容許加共享鎖)。但不容許其餘操做,除非我釋放了更新鎖。T1 執行 select,加更新鎖。T2 運行,準備加更新鎖,但發現已經有一個更新鎖在那兒了,只好等。當後來有 user三、user4...須要查詢 table 表中的數據時,並不會由於 T1的 select 在執行就被阻塞,照樣能查詢,相比起例6,這提升了效率。
某商品,用戶購買後庫存數應-1,而某兩個或多個用戶同時購買,此時三個執行程序均同時讀得庫存爲n,以後進行了一些操做,最後將均執行update table set 庫存數=n-1,那麼,很顯然這是錯誤的。
其實說白了也就是排他鎖。
靠表設計和代碼來實現。
update table set num=num-1 where id=10 and version=23
複製代碼
這樣,保證了修改的數據是和它查詢出來的數據是一致的,而其餘執行程序未進行修改。固然,若是更新失敗,表示在更新操做以前,有其餘執行程序已經更新了該庫存數,那麼就能夠嘗試重試來保證更新成功。爲了儘量避免更新失敗,能夠合理調整重試次數(阿里巴巴開發手冊規定重試次數不低於三次)。
對於以上,能夠看得出來樂觀鎖和悲觀鎖的區別:
悲觀鎖使用了排他鎖,當程序獨佔鎖時,其餘程序就連查詢都是不容許的,致使吞吐較低。若是在查詢較多的狀況下,可以使用樂觀鎖。
樂觀鎖更新有可能會失敗,甚至是更新幾回都失敗,這是有風險的。因此若是寫入較頻繁,對吞吐要求不高,可以使用悲觀鎖。
也就是一句話:多讀用樂觀鎖,多寫用悲觀鎖。