MySQL更新死鎖問題


之前寫了一篇博客 http://leihuang.org/2015/07/22/concurrence-lock/

就是如何利用樂觀鎖來解決並發問題,但是項目推到線上后就報錯了,如下

-08-13 15:12:44 [ERROR] com.zhubajie.coupon.app.CouponReceiveAppServiceImpl {CouponReceiveAppServiceImpl.java:50} - ### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction ### The error may involve defaultParameterMap ### The error occurred while setting parameters ### SQL: UPDATE cpn_core_coupon SET coup_num_usr = coup_num_usr + 1 WHERE coup_usr = ? AND spec_id = ? AND coup_num_usr < ? ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction ; SQL []; Deadlock found when trying to get lock; try restarting transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

錯誤中提示下面這條sql發生了死鎖

UPDATE coupon SET coup_num_usr = coup_num_usr + 1 WHERE coup_usr = ? AND spec_id = ? AND coup_num_usr < ?

首先我們來看下coupon的表結構

CREATE TABLE `coupon` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT ' ', `spec_id` char(20) NOT NULL COMMENT '優惠券活動編號', `coup_usr` char(11) DEFAULT NULL COMMENT '優惠券用戶', `coup_num_usr` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '該用戶已領取該活動的券數量', PRIMARY KEY (`id`), KEY `coup_usr_idx` (`coup_usr`), KEY `spec_idx` (`spec_id`) ) ENGINE=InnoDB AUTO_INCREMENT=8508 DEFAULT CHARSET=utf8 COMMENT='優惠券';

其中coup usr和spec id是索引,

mysql的事務支持與存儲引擎有關,MyISAM不支持事務,INNODB支持事務,更新時采用的是行級鎖。這里采用的是INNODB做存儲引擎,意味着會將update語句做為一個事務來處理。前面提到行級鎖必須建立在索引的基礎,這條更新語句用到了索引,所以這里肯定會加上行級鎖。 行級鎖並不是直接鎖記錄,而是鎖索引,如果一條SQL語句用到了主鍵索引,mysql會鎖住主鍵索引;如果一條語句操作了非主鍵索引,mysql會先鎖住非主鍵索引,再鎖定主鍵索引。 這個update語句會執行以下步驟: 1、由於用到了非主鍵索引,首先需要獲取普通索引上的行級鎖 2、緊接着根據主鍵進行更新,所以需要獲取主鍵上的行級鎖; 3、更新完畢后,提交,並釋放所有鎖。

如果在步驟1和2之間突然插入一條語句:UPDATE coupon SET coup num usr = coup num usr + 1 WHERE coup usr = ? AND specid = ? AND coup num usr < ?

就會發生死鎖的情況,因為一條語句獲取了普通索引的鎖,等待主鍵鎖,另外一條語句獲取了主鍵鎖,等待非主鍵索引,這樣就出現了死鎖.

如何來解決update ... where ...語句的死鎖問題呢?我們可以對其進行分離,首先利用where條件找到主鍵,然后再利用這些主鍵去更新數據。

因為select * where ...語句是沒有鎖的,所以不存在會鎖上where條件里面的字段,也就不會發生死鎖的情況,只有在update的時候回鎖上主鍵。

所以改成下面兩條語句

SELECT id WHERE coup_usr = ? AND spec_id = ? UPDATE coupon SET coup_num_usr = coup_num_usr + 1 WHERE id = ? AND coup_num_usr < ?

第一條語句找出所有需要更新行的主鍵id,然后再一條一條更新。

在采用INNODB的MySQL中,更新操作默認會加行級鎖,行級鎖是基於索引的,在分析死鎖之前需要查詢一下mysql的執行計划,看看是否用到了索引,用到了哪個索引,對於沒有用索引的操作會采用表級鎖。如果操作用到了主鍵索引會先在主鍵索引上加鎖,然后在其他索引上加鎖,否則加鎖順序相反。在並發度高的應用中,批量更新一定要帶上記錄的主鍵,優先獲取主鍵上的鎖,這樣可以減少死鎖的發生。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM