加鎖情況與死鎖原因分析
創新互聯專注于做網站、成都網站建設、網頁設計、網站制作、網站開發。公司秉持“客戶至上,用心服務”的宗旨,從客戶的利益和觀點出發,讓客戶在網絡營銷中找到自己的駐足之地。尊重和關懷每一位客戶,用嚴謹的態度對待客戶,用專業的服務創造價值,成為客戶值得信賴的朋友,為客戶解除后顧之憂。
為方便大家復現,完整表結構和數據如下:
CREATE TABLE `t3` (
`c1` int(11) NOT NULL AUTO_INCREMENT,
`c2` int(11) DEFAULT NULL,
PRIMARY KEY (`c1`),
UNIQUE KEY `c2` (`c2`)
) ENGINE=InnoDB
insert into t3 values(1,1),(15,15),(20,20);
在 session1 執行 commit 的瞬間,我們會看到 session2、session3 的其中一個報死鎖。這個死鎖是這樣產生的:
1.?session1 執行 delete ?會在唯一索引 c2 的 c2 = 15 這一記錄上加 X lock(也就是在MySQL 內部觀測到的:X Lock but not gap);
2.?session2 和 session3 在執行 insert 的時候,由于唯一約束檢測發生唯一沖突,會加 S Next-Key Lock,即對 (1,15] 這個區間加鎖包括間隙,并且被 seesion1 的 X Lock 阻塞,進入等待;
3.?session1 在執行 commit 后,會釋放 X Lock,session2 和 session3 都獲得 S Next-Key Lock;
4.?session2 和 session3 繼續執行插入操作,這個時候 INSERT INTENTION LOCK(插入意向鎖)出現了,并且由于插入意向鎖會被 gap 鎖阻塞,所以 session2 和 session3 互相等待,造成死鎖。
死鎖日志如下:
請點擊輸入圖片描述
INSERT INTENTION LOCK
在之前的死鎖分析第四點,如果不分析插入意向鎖,也是會造成死鎖的,因為插入最終還是要對記錄加 X Lock 的,session2 和 session3 還是會互相阻塞互相等待。
但是插入意向鎖是客觀存在的,我們可以在官方手冊中查到,不可忽略:
Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.
插入意向鎖其實是一種特殊的 gap lock,但是它不會阻塞其他鎖。假設存在值為 4 和 7 的索引記錄,嘗試插入值 5 和 6 的兩個事務在獲取插入行上的排它鎖之前使用插入意向鎖鎖定間隙,即在(4,7)上加 gap lock,但是這兩個事務不會互相沖突等待。
當插入一條記錄時,會去檢查當前插入位置的下一條記錄上是否存在鎖對象,如果下一條記錄上存在鎖對象,就需要判斷該鎖對象是否鎖住了 gap。如果 gap 被鎖住了,則插入意向鎖與之沖突,進入等待狀態(插入意向鎖之間并不互斥)。總結一下這把鎖的屬性:
1. 它不會阻塞其他任何鎖;
2. 它本身僅會被 gap lock 阻塞。
在學習 MySQL 過程中,一般只有在它被阻塞的時候才能觀察到,所以這也是它常常被忽略的原因吧...
GAP LOCK
在此例中,另外一個重要的點就是 gap lock,通常情況下我們說到 gap lock 都只會聯想到 REPEATABLE-READ 隔離級別利用其解決幻讀。但實際上在 READ-COMMITTED 隔離級別,也會存在 gap lock ,只發生在:唯一約束檢查到有唯一沖突的時候,會加 S Next-key Lock,即對記錄以及與和上一條記錄之間的間隙加共享鎖。
通過下面這個例子就能驗證:
請點擊輸入圖片描述
這里 session1 插入數據遇到唯一沖突,雖然報錯,但是對 (15,20] 加的 S Next-Key Lock 并不會馬上釋放,所以 session2 被阻塞。另外一種情況就是本文開始的例子,當 session2 插入遇到唯一沖突但是因為被 X Lock 阻塞,并不會立刻報錯 “Duplicate key”,但是依然要等待獲取 S Next-Key Lock 。
有個困惑很久的疑問:出現唯一沖突需要加 S Next-Key Lock 是事實,但是加鎖的意義是什么?還是說是通過 S Next-Key Lock 來實現的唯一約束檢查,但是這樣意味著在插入沒有遇到唯一沖突的時候,這個鎖會立刻釋放,這不符合二階段鎖原則。這點希望能與大家一起討論得到好的解釋。
如果是在 REPEATABLE-READ,除以上所說的唯一約束沖突外,gap lock 的存在是這樣的:
普通索引(非唯一索引)的S/X Lock,都帶 gap 屬性,會鎖住記錄以及前1條記錄到后1條記錄的左閉右開區間,比如有[4,6,8]記錄,delete 6,則會鎖住[4,8)整個區間。
對于 gap lock,相信 DBA 們的心情是一樣一樣的,所以我的建議是:
1. 在絕大部分的業務場景下,都可以把 MySQL 的隔離界別設置為 READ-COMMITTED;
2. 在業務方便控制字段值唯一的情況下,盡量減少表中唯一索引的數量。
鎖沖突矩陣
前面我們說的 GAP LOCK 其實是鎖的屬性,另外我們知道 InnoDB 常規鎖模式有:S 和 X,即共享鎖和排他鎖。鎖模式和鎖屬性是可以隨意組合的,組合之后的沖突矩陣如下,這對我們分析死鎖很有幫助:
請點擊輸入圖片描述
以前參加過一個庫存系統,由于其業務復雜性,搞了很多個應用來支撐。這樣的話一份庫存數據就有可能同時有多個應用來修改庫存數據。
比如說,有定時任務域xx.cron,和SystemA域和SystemB域這幾個JAVA應用,可能同時修改同一份庫存數據。如果不做協調的話,就會有臟數據出現。
對于跨JAVA進程的線程協調,可以借助外部環境,例如DB或者Redis。下文介紹一下如何使用DB來實現分布式鎖。
本文設計的分布式鎖的交互方式如下:
在使用synchronized關鍵字的時候,必須指定一個鎖對象。
進程內的線程可以基于obj來實現同步。obj在這里可以理解為一個鎖對象。如果線程要進入synchronized代碼塊里,必須先持有obj對象上的鎖。這種鎖是JAVA里面的內置鎖,創建的過程是線程安全的。那么借助DB,如何保證創建鎖的過程是線程安全的呢?
可以利用DB中的UNIQUE KEY特性,一旦出現了重復的key,由于UNIQUE KEY的唯一性,會拋出異常的。在JAVA里面,是 SQLIntegrityConstraintViolationException 異常。
transaction_id是事務Id,比如說,可以用
來組裝一個transaction_id,表示某倉庫某銷售模式下的某個條碼資源。不同條碼,當然就有不同的transaction_id。如果有兩個應用,拿著相同的transaction_id來創建鎖資源的時候,只能有一個應用創建成功。
在寫操作頻繁的業務系統中,通常會進行分庫,以降低單數據庫寫入的壓力,并提高寫操作的吞吐量。如果使用了分庫,那么業務數據自然也都分配到各個數據庫上了。
在這種水平切分的多數據庫上使用DB分布式鎖,可以自定義一個DataSouce列表。并暴露一個 getConnection(String transactionId) 方法,按照transactionId找到對應的Connection。
實現代碼如下:
首先編寫一個initDataSourceList方法,并利用Spring的PostConstruct注解初始化一個DataSource 列表。相關的DB配置從db.properties讀取。
DataSource使用阿里的DruidDataSource。
接著最重要的一個實現getConnection(String transactionId)方法。實現原理很簡單,獲取transactionId的hashcode,并對DataSource的長度取模即可。
連接池列表設計好后,就可以實現往distributed_lock表插入數據了。
接下來利用DB的 select for update 特性來鎖住線程。當多個線程根據相同的transactionId并發同時操作 select for update 的時候,只有一個線程能成功,其他線程都block住,直到 select for update 成功的線程使用commit操作后,block住的所有線程的其中一個線程才能開始干活。
我們在上面的DistributedLock類中創建一個lock方法。
當線程執行完任務后,必須手動的執行解鎖操作,之前被鎖住的線程才能繼續干活。在我們上面的實現中,其實就是獲取到當時 select for update 成功的線程對應的Connection,并實行commit操作即可。
那么如何獲取到呢?我們可以利用ThreadLocal。首先在DistributedLock類中定義
每次調用lock方法的時候,把Connection放置到ThreadLocal里面。我們修改lock方法。
這樣子,當獲取到Connection后,將其設置到ThreadLocal中,如果lock方法出現異常,則將其從ThreadLocal中移除掉。
有了這幾步后,我們可以來實現解鎖操作了。我們在DistributedLock添加一個unlock方法。
畢竟是利用DB來實現分布式鎖,對DB還是造成一定的壓力。當時考慮使用DB做分布式的一個重要原因是,我們的應用是后端應用,平時流量不大的,反而關鍵的是要保證庫存數據的正確性。對于像前端庫存系統,比如添加購物車占用庫存等操作,最好別使用DB來實現分布式鎖了。
如果想鎖住多份數據該怎么實現?比如說,某個庫存操作,既要修改物理庫存,又要修改虛擬庫存,想鎖住物理庫存的同時,又鎖住虛擬庫存。其實也不是很難,參考lock方法,寫一個multiLock方法,提供多個transactionId的入參,for循環處理就可以了。這個后續有時間再補上。
重新啟動MySQL:
[root@bogon ~]# /etc/rc.d/init.d/mysql restart
Shutting down MySQL [ 確定 ]
Starting MySQL. [ 確定 ]
[root@bogon ~]# mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.1.22-rc-community-log MySQL Community Edition (GPL)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql 為并發事務同時對一條記錄進行讀寫時,提出了兩種解決方案:
1)使用 mvcc 的方法,實現多事務的并發讀寫,但是這種讀只是“快照讀”,一般讀的是歷史版本數據,還有一種是“當前讀”,一般加鎖實現“當前讀”,或者 insert、update、delete 也是當前讀。
2)使用加鎖的方法,鎖分為共享鎖(讀鎖),排他鎖(寫鎖)
快照讀:就是select
當前讀:特殊的讀操作,插入/更新/刪除操作,屬于當前讀,處理的都是當前的數據,需要加鎖。
mysql 在 RR 級別怎么處理幻讀的呢?一般來說,RR 級別通過 mvcc 機制,保證讀到低于后面事務的數據。但是 select for update 不會觸發 mvcc,它是當前讀。如果后面事務插入數據并提交,那么在 RR 級別就會讀到插入的數據。所以,mysql 使用 行鎖 + gap 鎖(簡稱 next-key 鎖)來防止當前讀的時候插入。
Gap Lock在InnoDB的唯一作用就是防止其他事務的插入操作,以此防止幻讀的發生。
Innodb自動使用間隙鎖的條件:
本文名稱:mysql怎么設置排鎖 21款寶來帶行車記錄儀嗎
網頁路徑:http://vcdvsql.cn/article46/ddojpeg.html
成都網站建設公司_創新互聯,為您提供網站設計公司、企業網站制作、移動網站建設、網站策劃、網站設計、面包屑導航
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯