继上一次SSM+Redis高并发抢红包之-超发现象的问题解决,这里我们使用悲观锁来解决
首先我们需要了解什么是悲观锁(也叫独占锁)
悲观锁是一种利用数据库内部机制提供的锁的方法,也就是对更新的数据加锁
我这里使用的是mysql, 存储引擎是InnoDB 这个是支持事务和行锁的。而这里我们使用的就是行锁
关于mysql相关锁及其原理,这里可以看下这篇博客,我觉得很nice MySQL学习之——锁(行锁、表锁、页锁、乐观锁、悲观锁等)
在这里悲观锁的实现方式,就是在并发期间一旦有一个事务持有数据库记录的锁,那么其他的线程将不能再对数据进行更新。
在这次实验中,我们只需要添加少量代码,代码如下:
1.在RedPacketDao 接口类中添加
/*
* 获取红包信息,使用悲观锁
* @param id 红包id
* @return 红包具体信息
*/
public RedPacket getRedPacketForUpdate(Long id);
2.在RedPacket.xml中添加getRedPacketForUpdate的sql语句
<!-- 查询红包具体信息,使用悲观锁 -->
<select id="getRedPacketForUpdate" parameterType="long"
resultType="com.pojo.RedPacket">
select id, user_id as userId, amount, send_date as
sendDate, total,
unit_amount as unitAmount, stock, version, note from
T_RED_PACKET
where id = #{id} for update
</select>
如果仔细观察,发现和超发现象获取红包信息的sql语句差不多,就是多了for update语句
如果在sql语句中加了for update ,那么就意味着将持有对数据库记录的行更新锁,因为这里我们的id 设置为primary key ,而当前查询为主键查询,那么这里就只会是对行加锁。如果使用非主键查询,那么就需要考虑是否对全表加锁的问题。
然后在UserRedPacketServiceImpl 接口类里 重新添加获取红包信息的接口,如下面代码中注释for update那行代码
public int grapRedPacket(Long redPacketId, Long userId) {
//获取红包信息
//RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);
//获取红包信息,for update
RedPacket redPacket = redPacketDao.getRedPacketForUpdate(redPacketId);
//当前小红包库存大于0
if(redPacket.getStock() > 0) {
redPacketDao.decreaseRedPacket(redPacketId);
//生成抢红包信息
UserRedPacket userRedPacket = new UserRedPacket();
userRedPacket.setRedPacketId(redPacketId);
userRedPacket.setRedPacketId(redPacketId);
userRedPacket.setUserId(userId);
userRedPacket.setAmount(redPacket.getUnitAmount());
userRedPacket.setNote("抢红包 "+ redPacketId);
//插入抢红包信息
int result = userRedPacketDao.grapRedPacket(userRedPacket);
return result;
}
return FAILED;
}
最后我们再向t_red_packet插入和上次一样的红包数据
然后测试
最后在数据库中进行查询,如下图所示
这里已经解决了超发的问题,下面看下性能方面
这里无限接近1分钟了,比超发现象那么多了几S,这也只是20000个红包,而且当前就单独使用了一个行锁罢了,如果在后面再加几个锁进来,没有得到锁的线程不断的被挂起(这里挂起也是消耗系统资源的),当某个资源释放锁,其他线程又一哄而上,那么久而久之,系统的性能将不断下降。所以又有一些大神想出了使用乐观锁的机制,关于乐观锁,我们下一个博客 SSM+Redis高并发抢红包之-乐观锁再说