Java秒殺系統實戰系列~數據庫級別Sql的優化與代碼的調整

摘要:

本篇博文是「Java秒殺系統實戰系列文章」的第十三篇,從本篇文章開始咱們將進入「秒殺代碼優化」環節,本文將首先從數據庫級別Sql的優化入手,結合調整秒殺相關的部分核心代碼,實現初步的優化!mysql

內容:

上篇文章咱們暴露出了「秒殺接口」在面對高併發請求的場景下所出現的「超賣」、「重複秒殺」等問題,並對相應的問題進行了分析,而後就沒有而後了……(事了拂衣去!)git

問題既然落在咱們的手裏,那麼身爲一名程序猿,那是沒有理由迴避的。經過分析該「秒殺接口」的核心代碼,能夠發如今數據庫層面,其涉及的Sql咱們仍是能夠動一動手腳的!其調整後的「秒殺核心業務邏輯」的完整源代碼以下所示:算法

//商品秒殺核心業務邏輯的處理-mysql的優化
@Override
public Boolean killItemV2(Integer killId, Integer userId) throws Exception {
    Boolean result=false;

    //TODO:判斷當前用戶是否已經搶購過當前商品
    if (itemKillSuccessMapper.countByKillUserId(killId,userId) <= 0){
        //A  查詢待秒殺商品詳情
        ItemKill itemKill=itemKillMapper.selectByIdV2(killId);

        //TODO:判斷是否能夠被秒殺canKill=1?
        if (itemKill!=null && 1==itemKill.getCanKill() && itemKill.getTotal()>0){
            //B  扣減庫存-減一
            int res=itemKillMapper.updateKillItemV2(killId);

            //TODO:扣減是否成功?是-生成秒殺成功的訂單,同時通知用戶秒殺成功的消息
            if (res>0){
                commonRecordKillSuccessInfo(itemKill,userId);
                result=true;
            }
        }
    }else{
        throw new Exception("您已經搶購過該商品了!");
    }
    return result;
}複製代碼


首先是對於 註釋A 那裏的調整,即在獲取「秒殺商品詳情」時,咱們限定了「可秒殺商品的數量total須要大於0」,其對應的代碼爲:itemKillMapper.updateKillItemV2(killId);完整的動態Sql以下所示:
sql

<!--獲取秒殺詳情V2-->
<select id="selectByIdV2" resultType="com.debug.kill.model.entity.ItemKill">
  SELECT
    a.*,
    b.name AS itemName,
    (CASE WHEN (now() BETWEEN a.start_time AND a.end_time)
      THEN 1
     ELSE 0
     END)  AS canKill
  FROM item_kill AS a LEFT JOIN item AS b ON b.id = a.item_id
  WHERE a.is_active = 1 AND a.id =#{id} AND a.total>0
</select>複製代碼


而後是 註釋B 對應的優化調整,即在扣減庫存時,咱們除了能夠保證正常減1的操做以外,還須要保證扣減完以後的數量大於0,即只有在保證扣減完以後的數量大於0之下,該Sql操做後受影響的行數爲1,對應的代碼爲:itemKillMapper.updateKillItemV2(killId); 其對應的動態Sql以下所示:
數據庫

<!--搶購商品,剩餘數量減一-->
<update id="updateKillItemV2">
  UPDATE item_kill
  SET total = total - 1
  WHERE id = #{killId} AND total>0
</update>複製代碼


至此,咱們在秒殺核心業務邏輯的優化層面~數據庫級別Sql的優化 已經搞完了!除此以外,咱們還在代碼層面進行優化,以下所示:
bash

private void commonRecordKillSuccessInfo(ItemKill kill, Integer userId) throws Exception{
  //TODO:記錄搶購成功後生成的秒殺訂單記錄

  ItemKillSuccess entity=new ItemKillSuccess();
  String orderNo=String.valueOf(snowFlake.nextId());

  //entity.setCode(RandomUtil.generateOrderCode());   //傳統時間戳+N位隨機數
  entity.setCode(orderNo); //雪花算法
  entity.setItemId(kill.getItemId());
  entity.setKillId(kill.getId());
  entity.setUserId(userId.toString());
  entity.setStatus(SysConstant.OrderStatus.SuccessNotPayed.getCode().byteValue());
  entity.setCreateTime(DateTime.now().toDate());
  //TODO:學以至用,觸類旁通 -> 仿照單例模式的雙重檢驗鎖寫法
  if (itemKillSuccessMapper.countByKillUserId(kill.getId(),userId) <= 0){
      int res=itemKillSuccessMapper.insertSelective(entity);

      if (res>0){
          //TODO:進行異步郵件消息的通知=rabbitmq+mail
          rabbitSenderService.sendKillSuccessEmailMsg(orderNo);

          //TODO:入死信隊列,用於 「失效」 超過指定的TTL時間時仍然未支付的訂單
          rabbitSenderService.sendKillSuccessOrderExpireMsg(orderNo);
      }
  }
}複製代碼


即咱們在「用戶秒殺成功生成訂單記錄」的代碼加入了相似於「單例模式」中的「雙重檢驗鎖」,即在生成訂單記錄,再次判斷一下「當前用戶是否已經真的沒有搶購過該商品」!微信

在後面的篇章中,咱們將開始搬上「中間件」這一利器,並結合本文所介紹Sql的優化和調整後的代碼,完全解決高併發壓力測試的場景下出現的「庫存超賣」、「重複秒殺」等亂七八糟的問題!併發

補充:

一、目前,這一秒殺系統的總體構建與代碼實戰已經所有完成了,完整的源代碼數據庫地址能夠來這裏下載:gitee.com/steadyjack/… 記得Fork跟Star啊!!app

二、最後,不要忘記了關注一下Debug的技術微信公衆號:dom

相關文章
相關標籤/搜索