无论什么锁JAVA的synchronized也好,还是MYSQL的锁都好,要注意分布式环境与单机环境
1.乐观锁
乐观认为并发不高,甚至没有并发。其中一种实现方式依靠在表中多加一个版本号字段,每次查询更新就按这个版本号,假设在修改时版本号与数据库不一致,就需要尝试重连(重新执行)。
查询
SELECT * FROM user WHERE id = #{id}
修改
UPDATE user SET .... WHERE id = #{id} AND version = #{version}
缺点:当出现并发时,可能会一直尝试重连
2.悲观锁
悲观认为并发经常出现,比如用户的余额问题。需要注意在Spring-Boot中悲观锁需要再事务逻辑中才会生效。悲观锁分为两种
悲观锁锁类 = 查询字段为索引 ? 行级锁 : 表级锁
实现方式
Mapper(核心为末尾的FOR UPDATE关键词)
@Select("SELECT * FROM person WHERE id = #{0} FOR UPDATE")
Person selectMoneyById(Integer id);
Service(核心为开头的事务)
@Transactional(rollbackFor = Exception.class)
@Override
public boolean deductTicket(Integer aud, Integer ticket)
log.info("线程{}:start", Thread.currentThread().getName());
Person person = baseMapper.selectMoneyById(aud);
// 上面会阻塞,直到解锁(事务结束)
log.info("线程{}:mid => {}", Thread.currentThread().getName(), person)
if (person.getTicket() < ticket) {
log.info("余额不足 => aud:{},targetTicket:{},nowTicket:{}", aud, ticket, person.getTicket());
log.info("线程{}:end", Thread.currentThread().getName());
return false;
}
// 加个延时看效果
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
baseMapper.updateById(person.setTicket(person.getTicket() - ticket));
log.info("扣款成功 => aud:{},targetTicket:{},nowTicket:{}", aud, ticket, person.getTicket());
log.info("线程{}:end", Thread.currentThread().getName());
return true;
}