https://blog.csdn.net/a12345555555/article/details/72828366
-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的執行計划,看看是否用到了索引,用到了哪個索引,對於沒有用索引的操作會采用表級鎖。如果操作用到了主鍵索引會先在主鍵索引上加鎖,然后在其他索引上加鎖,否則加鎖順序相反。在並發度高的應用中,批量更新一定要帶上記錄的主鍵,優先獲取主鍵上的鎖,這樣可以減少死鎖的發生。
我在項目中遇到的問題:
1.在更新設備時,使用了批量更新。
解決辦法:不要使用批量更新,通過循環的方式一個一個的更新,條件要使用唯一主鍵uuid
2.更新斷網記錄時,使用了批量更新,並且條件不是唯一主鍵
解決辦法:不要使用批量更新,查詢出斷網記錄表的每一條數據的主鍵uuid,使用唯一主鍵uuid作為更新條件,循環的方式一個一個進行更新。