select * from student;
执行以下代码:开100个线程执行set age = age + 1
// StudentController.class
public void update (Integer id) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
studentService.addAge(id);
}).start();
}
}
// StudentServiceImpl.class
@Override
@Transactional(rollbackFor = Exception.class)
public void addAge (Integer id) {
Integer oldAge = studentMapper.queryAge(id);
StudentDO update = new StudentDO();
update.setId(id);
update.setAge(oldAge + 1);
studentMapper.updateByPrimaryKeySelective(update);
}
// StudentMapper.xml
<select id="queryAge" resultType="int">
select age from student where id = #{id}
</select>
age的预期结果应该是100,但是只有23
使用悲观锁解决
修改sql为
select age from student where id = #{id} for update
注意:
1.addAge方法需要加事务,悲观锁才会生效,因为悲观锁是在事务开始前加锁,事务结束时释放锁
2.for update是排他锁,事务A获取了一行记录的排他锁,其他事务再获取该行记录的共享锁或者排他锁会被阻塞,也就是其他事务可以执行普通的select,但是不能执行select ... in share mode或者update/delete(update/delete会加排他锁)
使用乐观锁解决
// StudentServiceImpl.class
@Override
@Transactional(rollbackFor = Exception.class)
public void addAge (Integer id) {
int tryTimes = 100;
for (int i = 0; i < tryTimes; i++) {
if (addAgeInternal(id) > 0) {
break;
}
// 每次尝试失败后睡眠一段时间
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(100));
} catch (Exception e) {
log.error("线程睡眠异常", e);
Thread.currentThread().interrupt();
}
}
}
private int addAgeInternal(Integer id) {
// 旧的信息
StudentDO old = studentMapper.queryAge(id);
// id和version都匹配上才更新数据
Weekend weekend = new Weekend(StudentDO.class);
WeekendCriteria<StudentDO, Object> weekendCriteria = weekend.weekendCriteria();
weekendCriteria.andEqualTo(StudentDO::getId, id)
.andEqualTo(StudentDO::getVersion, old.getVersion());
StudentDO update = new StudentDO();
update.setAge(old.getAge() + 1);
// 每次更新把version + 1
update.setVersion(old.getVersion() + 1);
return studentMapper.updateByExampleSelective(update, weekend);
}
总结
悲观锁是真正的使用了mysql提供的锁,乐观锁其实没有用锁,只是自己写逻辑控制并发