繼上一次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高并發搶紅包之-樂觀鎖再說