mysql记录锁与互斥锁区别_MySQL的各种锁认知
一、相關名詞
|--表級鎖(鎖定整個表)
|--頁級鎖(鎖定一頁)
|--行級鎖(鎖定一行)
|--共享鎖(S鎖,MyISAM 叫做讀鎖)
|--排他鎖(X鎖,MyISAM 叫做寫鎖)
|--悲觀鎖(抽象性,不真實存在這個鎖)
|--樂觀鎖(抽象性,不真實存在這個鎖)
二、InnoDB與MyISAM
Mysql 在5.5之前默認使用 MyISAM 存儲引擎,之后使用 InnoDB 。查看當前存儲引擎:
show variables like '%storage_engine%';
MyISAM 操作數據都是使用的表鎖,你更新一條記錄就要鎖整個表,導致性能較低,并發不高。當然同時它也不會存在死鎖問題。
而 InnoDB 與 MyISAM 的最大不同有兩點:一是 InnoDB 支持事務;二是 InnoDB 采用了行級鎖。也就是你需要修改哪行,就可以只鎖定哪行。
在 Mysql 中,行級鎖并不是直接鎖記錄,而是鎖索引。索引分為主鍵索引和非主鍵索引兩種,如果一條sql 語句操作了主鍵索引,Mysql 就會鎖定這條主鍵索引;如果一條語句操作了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。
InnoDB 行鎖是通過給索引項加鎖實現的,如果沒有索引,InnoDB 會通過隱藏的聚簇索引來對記錄加鎖。也就是說:如果不通過索引條件檢索數據,那么InnoDB將對表中所有數據加鎖,實際效果跟表鎖一樣。因為沒有了索引,找到某一條記錄就得掃描全表,要掃描全表,就得鎖定表。
三、共享鎖與排他鎖
1.首先說明:數據庫的增刪改操作默認都會加排他鎖,而查詢不會加任何鎖。
|--共享鎖:對某一資源加共享鎖,自身可以讀該資源,其他人也可以讀該資源(也可以再繼續加共享鎖,即 共享鎖可多個共存),但無法修改。要想修改就必須等所有共享鎖都釋放完之后。語法為:
select * from table lock in share mode
|--排他鎖:對某一資源加排他鎖,自身可以進行增刪改查,其他人無法進行任何操作。語法為:
select * from table for update --增刪改自動加了排他鎖
2.下面援引例子說明(援自:http://blog.csdn.net/samjustin1/article/details/52210125):
這里用T1代表一個數據庫執行請求,T2代表另一個請求,也可以理解為T1為一個線程,T2 為另一個線程。
例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 transelect * from table lock in share modeupdate table set column1='hello'
T2:begin transelect * 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 tranupdate table set column1='hello' where id=10
T2:begin tranupdate table set column1='world' where id=20
這種語句雖然最為常見,很多人覺得它有機會產生死鎖,但實際上要看情況
|--如果id是主鍵(默認有主鍵索引),那么T1會一下子找到該條記錄(id=10的記錄),然后對該條記錄加排他鎖,T2,同樣,一下子通過索引定位到記錄,然后對id=20的記錄加排他鎖,這樣T1和T2各更新各的,互不影響。T2也不需要等。
|--如果id是普通的一列,沒有索引。那么當T1對id=10這一行加排他鎖后,T2為了找到id=20,需要對全表掃描。但因為T1已經為一條記錄加了排他鎖,導致T2的全表掃描進行不下去(其實是因為T1加了排他鎖,數據庫默認會為該表加意向鎖,T2要掃描全表,就得等該意向鎖釋放,也就是T1執行完成),就導致T2等待。
死鎖怎么解決呢?一種辦法是,如下:
例6:-------------------------------------------------------------------------------------------------------------------------------------
T1:begin transelect * from table for updateupdate table set column1='hello'
T2:begin transelect * 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 運行,準備加更新鎖,但發現已經有一個更新鎖在那兒了,只好等。當后來有 user3、user4...需要查詢 table 表中的數據時,并不會因為 T1 的 select 在執行就被阻塞,照樣能查詢,相比起例6,這提高了效率。
后面還有意向鎖和計劃鎖:意向鎖即是:某行修改時,自動加上了排他鎖,同時會默認給該表加意向鎖,表示里面有記錄正被鎖定,這時,其他人就不可以對該表加表鎖了。如果沒有意向鎖這個類似指示燈的東西存在,其他人加表鎖之前就得掃描全表,查看是否有記錄正被鎖定,效率低下。而計劃鎖這些,和程序員關系不大,就沒去了解了。
悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類似于write_condition機制的其實都是提供的樂觀鎖。
兩種鎖各有優缺點,不可認為一種好于另一種,像樂觀鎖適用于寫比較少的情況下,即沖突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果經常產生沖突,上層應用會不斷的進行retry,這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。
擴展:
本質上,數據庫的樂觀鎖做法和悲觀鎖做法主要就是解決下面假設的場景,避免丟失更新問題: 一個比較清楚的場景 下面這個假設的實際場景可以比較清楚的幫助我們理解這個問題: 假設當當網上用戶下單買了本書,這時數據庫中有條訂單號為001的訂單,其中有個status字段是’有效’,表示該訂單是有效的; 后臺管理人員查詢到這條001的訂單,并且看到狀態是有效的 用戶發現下單的時候下錯了,于是撤銷訂單,假設運行這樣一條SQL: update order_table set status = ‘取消’ where order_id = 001; 后臺管理人員由于在b這步看到狀態有效的,這時,雖然用戶在c這步已經撤銷了訂單,可是管理人員并未刷新界面,看到的訂單狀態還是有效的,于是點擊”發貨”按鈕,將該訂單發到物流部門,同時運行類似如下SQL,將訂單狀態改成已發貨:update order_table set status = ‘已發貨’ where order_id = 001
觀點1:只有沖突非常嚴重的系統才需要悲觀鎖;“所有悲觀鎖的做法都適合于狀態被修改的概率比較高的情況,具體是否合適則需要根據實際情況判斷。”,表達的也是這個意思,不過說法不夠準確;的確,之所以用悲觀鎖就是因為兩個用戶更新同一條數據的概率高,也就是沖突比較嚴重的情況下,所以才用悲觀鎖。
觀點2:最后提交前作一次select for update檢查,然后再提交update也是一種樂觀鎖的做法,的確,這符合傳統樂觀鎖的做法,就是到最后再去檢查。但是wiki在解釋悲觀鎖的做法的時候,’It is not appropriate for use in web application development.’, 現在已經很少有悲觀鎖的做法了,所以我自己將這種二次檢查的做法也歸為悲觀鎖的變種,因為這在所有樂觀鎖里面,做法和悲觀鎖是最接近的,都是先select for update,然后update
在實際應用中我們在更新數據的時候,更嚴謹的做法是帶上更新前的“狀態”,如
update order_table set status = ‘取消’ where order_id = 001 and status = ‘待支付’ and ..........;
update order_table set status = ‘已發貨’ where order_id = 001 and status = ‘已支付’ and ..........;
然后在業務邏輯代碼里判斷更新的記錄數,為0說明數據庫已經被更新了,否則是正常的。
行鎖,表鎖
InnoDB存儲引擎中有行鎖以及表鎖,行鎖是InnoDB中默認的鎖。
表鎖:對整張表進行加鎖,在同一時刻整張表的所有記錄都被鎖住。
行鎖:只對表中的某一行記錄進行加鎖,表的其余行不會被占用,但是可能會出現死鎖。
關閉事務自動提交
查看一下表數據
接著我們更新一條數據
執行成功之后我們并沒有提交事務,這個時候這一條記錄已經是加了鎖的,所以我們在另外一個客戶端更新同樣的行記錄。
自然就報錯了,直接就等待超時了。這里證明已經加鎖了,接著我們來證明是行鎖還是表鎖。
當我們執行update的時候,是update 字段a=1的 所以我們在update字段a=2的時候,雖然沒有提交事務但是還是可以執行的,這里證明了InnoDB是行鎖的。
注意:行鎖必須有索引才能實現,否則就會自動鎖住全表,也就是表鎖,而InnoDB當有主鍵的時候,自動就會創建主鍵索引。
行鎖與表鎖的區別
行鎖
優點 :粒度小, 因為加鎖的只是一行數據。
缺點 :獲取、釋放所需要做的工作更多,并且容易發生死鎖。
鎖的優化:
合理設計索引
減少基于范圍的數據檢索過濾條件
盡量控制事務的大小,盡量使用較低的事務隔離級別
盡可能讓所有的數據檢索都通過索引來完成。
表鎖
優點:獲取跟釋放快,能避免死鎖(當執行update語句的時候,把整個表鎖住了,其他的sql無法執行,所以不會造成死鎖)
缺點:粒度太大,并發不夠高,當并發量較多的時候,鎖表會讓進程無法繼續執行sql。
死鎖
死鎖出現在行鎖中,假設現在有一個T1的session線程去update一個數據庫表table1 ,而且有一個T2的session線程去update一個數據庫表table2。
在沒有提交事務的時候,table1跟table2都已經進行了加鎖,這個時候,T1去操作了table2,那么這個時候因為table2的記錄加了鎖,那么T1會一直在等待,接著T2又同樣的去操作table1的表記錄,也同樣在等待,就造成了死鎖。
InnoDB 支持多粒度鎖(multiple granularity locking),它允許行級鎖與表級鎖共存,而意向鎖就是其中的一種表鎖。
意向鎖(Intention Locks)
需要強調一下,意向鎖是一種不與行級鎖沖突的表級鎖,這一點非常重要。意向鎖分為兩種:
意向共享鎖 (intention shared lock, IS):事務有意向對表中的某些行加 共享鎖 (S鎖) -- 事務要獲取某些行的 S 鎖,必須先獲得表的 IS 鎖。 SELECT column FROM table ... LOCK IN SHARE MODE;
意向排他鎖 (intention exclusive lock, IX):事務有意向對表中的某些行加 排他鎖 (X鎖) -- 事務要獲取某些行的 X 鎖,必須先獲得表的 IX 鎖。 SELECT column FROM table ... FOR UPDATE;
即:意向鎖是有數據引擎自己維護的,用戶無法手動操作意向鎖,在為數據行加共享 / 排他鎖之前,InooDB 會先獲取該數據行所在在數據表的對應意向鎖。
意向鎖要解決的問題
我們先來看一下百度百科上對意向鎖存在意義的描述:
如果另一個任務試圖在該表級別上應用共享或排它鎖,則受到由第一個任務控制的表級別意向鎖的阻塞。第二個任務在鎖定該表前不必檢查各個頁或行鎖,而只需檢查表上的意向鎖。
設想這樣一張 users 表:MySql,InnoDB,Repeatable-Read:users(id PK,name)
事務 A 獲取了某一行的排他鎖,并未提交:
SELECT * FROM users WHERE id = 6 FOR UPDATE;
事務 B 想要獲取 users 表的表鎖:
LOCK TABLES users READ;
因為共享鎖與排他鎖互斥,所以事務 B 在視圖對 users 表加共享鎖的時候,必須保證:
當前沒有其他事務持有 users 表的排他鎖。
當前沒有其他事務持有 users 表中任意一行的排他鎖 。
為了檢測是否滿足第二個條件,事務 B 必須在確保 users表不存在任何排他鎖的前提下,去檢測表中的每一行是否存在排他鎖。很明顯這是一個效率很差的做法,但是有了意向鎖之后,情況就不一樣了:
意向鎖的兼容互斥性
意向鎖是怎么解決這個問題的呢?首先,我們需要知道意向鎖之間的兼容互斥性:
即意向鎖之間是互相兼容的,emmm......那你存在的意義是啥?
雖然意向鎖和自家兄弟互相兼容,但是它會與普通的排他 / 共享鎖互斥:
注意:這里的排他 / 共享鎖指的都是表鎖!!!意向鎖不會與行級的共享 / 排他鎖互斥!!!
現在我們回到剛才 users 表的例子:
事務 A 獲取了某一行的排他鎖,并未提交:
SELECT * FROM users WHERE id = 6 FOR UPDATE;
此時 users 表存在兩把鎖:users 表上的意向排他鎖與 id 為 6 的數據行上的排他鎖。
事務 B 想要獲取 users 表的共享鎖:
LOCK TABLES users READ;
此時事務 B 檢測事務 A 持有 users 表的意向排他鎖,就可以得知事務 A 必然持有該表中某些數據行的排他鎖,那么事務 B 對 users 表的加鎖請求就會被排斥(阻塞),而無需去檢測表中的每一行數據是否存在排他鎖。
意向鎖的并發性
這就牽扯到我前面多次強調的一件事情:
意向鎖不會與行級的共享 / 排他鎖互斥!!!意向鎖不會與行級的共享 / 排他鎖互斥!!!意向鎖不會與行級的共享 / 排他鎖互斥!!!
重要的話要加粗說三遍,正因為如此,意向鎖并不會影響到多個事務對不同數據行加排他鎖時的并發性(不然我們直接用普通的表鎖就行了)。
最后我們擴展一下上面 users 表的例子來概括一下意向鎖的作用(一條數據從被鎖定到被釋放的過程中,可能存在多種不同鎖,但是這里我們只著重表現意向鎖):
事務 A 先獲取了某一行的排他鎖,并未提交:
SELECT * FROM users WHERE id = 6 FOR UPDATE;
事務 A 獲取了 users 表上的意向排他鎖。
事務 A 獲取了 id 為 6 的數據行上的排他鎖。
之后事務 B 想要獲取 users 表的共享鎖:
LOCK TABLES users READ;
事務 B 檢測到事務 A 持有 users 表的意向排他鎖。
事務 B 對 users 表的加鎖請求被阻塞(排斥)。
最后事務 C 也想獲取 users 表中某一行的排他鎖:
SELECT * FROM users WHERE id = 5 FOR UPDATE;
事務 C 申請 users 表的意向排他鎖。
事務 C 檢測到事務 A 持有 users 表的意向排他鎖。
因為意向鎖之間并不互斥,所以事務 C 獲取到了 users 表的意向排他鎖。
因為id 為 5 的數據行上不存在任何排他鎖,最終事務 C 成功獲取到了該數據行上的排他鎖。
總結
InnoDB 支持多粒度鎖,特定場景下,行級鎖可以與表級鎖共存。
意向鎖之間互不排斥,但除了 IS 與 S 兼容外,意向鎖會與 共享鎖 / 排他鎖 互斥。
IX,IS是表級鎖,不會和行級的X,S鎖發生沖突。只會和表級的X,S發生沖突。
意向鎖在保證并發性的前提下,實現了行鎖和表鎖共存且滿足事務隔離性的要求。
總結
以上是生活随笔為你收集整理的mysql记录锁与互斥锁区别_MySQL的各种锁认知的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: sql远程mysql服务器查询_sql
- 下一篇: php编程怎么和mysql链接_php编