就是如何利用乐观锁来解决并发问题,但是项目推到线上后就报错了,如下:
-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 spec
id = ? 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作为更新条件,循环的方式一个一个进行更新。