并發(fā)下的庫存如何扣塘娶?

并發(fā)下的庫存如何扣?

背景

業(yè)務(wù)反饋归斤,項(xiàng)目出現(xiàn)庫存超賣/負(fù)值現(xiàn)象。

原因

    //簡(jiǎn)易demo
    $conn = mysqli_connect('localhost','root','123456','shop') or die('數(shù)據(jù)庫連接失敗');
    $conn->query("SET NAMES 'UTF8'");
    $query = "SELECT num FROM stock where id = 1"; 
    $rs = $conn->query($query);
    $row = $rs-> fetch_array();
    
    $num = $row['num'];
    $reduce = 1;
    $update = 'update stock set num = num - '.$reduce.' where id = 1';
    if($conn->query($update)){
        echo 'success';
    }else{
        //重試
        $conn->query($update)
    };

假設(shè)庫存為5刁岸,用戶一次買了1個(gè)脏里,于是庫存扣減為4,如果更新失敗則重新設(shè)置虹曙,看似好像沒問題迫横。

問題1

庫存扣為負(fù)值

    $num = $row['num'];
    $reduce = 1;
    if($num >= $reduce){
        $update = 'update stock set num = num - '.$reduce.' where id = 1';
        if($conn->query($update)){
            echo 'success';
        }else{
            //重試
            $conn->query($update)
        }
    }

這樣就不會(huì)出現(xiàn)庫存為負(fù)值

問題2

非冪等操作,庫存重復(fù)扣得問題(庫存為5個(gè),用戶想買2個(gè)酝碳,因?yàn)椴l(fā)&重試矾踱,實(shí)際扣了4個(gè)庫存)

此時(shí),我們必須知道疏哗,“set num=num-$reduce”扣減操作是個(gè)“非冪等操作”呛讲,即每次sql執(zhí)行得到的結(jié)果不一樣,實(shí)際我們需要的是用戶重試多次得到的應(yīng)該是同樣的結(jié)果沃斤,這卻是個(gè)“冪等操作”圣蝎,所以換個(gè)思路,我們將“非冪等”的“扣減操作”改為“冪等”的“設(shè)置庫存操作”

    $num = $row['num'];
    $reduce = 1;
    $newNum = $num - $reduce;
    if($newNum >= 0){
        $update = 'update stock set num = '.$newNum.' where id = 1';
        if($conn->query($update)){
            echo 'success';
        }else{
            //重試
            $conn->query($update)
        }
    }
  

問題3

并發(fā)問題

一共5個(gè)庫存

A買了3衡瓶,庫存設(shè)置為2

B買了2個(gè)庫存徘公,庫存要設(shè)置為3

這兩個(gè)設(shè)置庫存并發(fā)執(zhí)行,庫存會(huì)先變成2哮针,再變成3关面,導(dǎo)致數(shù)據(jù)不一致(實(shí)際賣出了5件商品,但庫存只扣減了2十厢,最后一次設(shè)置庫存會(huì)覆蓋和掩蓋前一次并發(fā)操作)

其根本原因是等太,設(shè)置操作發(fā)生的時(shí)候,沒有檢查庫存與查詢出來的庫存有沒有變化蛮放,理論上:
庫存為5時(shí)缩抡,A的庫存設(shè)置才能成功
庫存為5時(shí),B的庫存設(shè)置才能成功
實(shí)際執(zhí)行的時(shí)候:
庫存為5包颁,A的set stock 2確實(shí)應(yīng)該成功
庫存變?yōu)?了瞻想,B的set stock 3應(yīng)該失敗掉

修改 (CAS原理)

    $num = $row['num'];
    $reduce = 1;
    $newNum = $num - $reduce;
    if($newNum >= 0){
        $update = 'update stock set num = '.$newNum.' where id = 1 and num = '.$num;
        if($conn->query($update)){
            echo 'success';
        }else{
            //重試
            $conn->query($update)
        }
    }

問題4 CAS引發(fā)的ABA問題

ABA問題
并發(fā)1(上):獲取出數(shù)據(jù)的初始值是A压真,后續(xù)計(jì)劃實(shí)施CAS樂觀鎖,期望數(shù)據(jù)仍是A的時(shí)候蘑险,修改才能成功
并發(fā)2:將數(shù)據(jù)修改成B
并發(fā)3:將數(shù)據(jù)修改回A
并發(fā)1(下):CAS樂觀鎖滴肿,檢測(cè)發(fā)現(xiàn)初始值還是A,進(jìn)行數(shù)據(jù)修改
ABA在多數(shù)庫存情況下不會(huì)引發(fā)業(yè)務(wù)問題佃迄,但是少數(shù)的情況下會(huì)出現(xiàn)錯(cuò)誤泼差。
所以,周全的辦法是也要解決ABA問題
ABA本質(zhì)上是“僅比對(duì)值”造成的問題呵俏,所以我們可以對(duì)庫存加上一個(gè)版本號(hào)來解決該問題堆缘,每修改一次數(shù)據(jù)則版本號(hào)變更一次

update stock set num=$new_num, version=$new_version where sid=$sid and version=$version_old
    $num = $row['num'];
        $oldVersion = $row['version '];
        $newVersion = $oldVersion++;
    $reduce = 1;
    $newNum = $num - $reduce;
    if($newNum >= 0){
        $update = 'update stock set num = '.$newNum.',version='.newVersion .' where id = 1 and version= '.$oldVersion ;
        if($conn->query($update)){
            echo 'success';
        }else{
            //重試
            $conn->query($update)
        }
    }

總結(jié)

高并發(fā)下庫存應(yīng)如下優(yōu)化

  • “設(shè)置庫存”保證冪等性
  • “設(shè)置庫存”,需要加上原有庫存的比較柴信,才允許設(shè)置成功套啤,能解決高并發(fā)下庫存扣減的一致性問題
  • CAS引發(fā)的ABA問題采用version比對(duì)解決
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市随常,隨后出現(xiàn)的幾起案子潜沦,更是在濱河造成了極大的恐慌,老刑警劉巖绪氛,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唆鸡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡枣察,警方通過查閱死者的電腦和手機(jī)争占,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來序目,“玉大人臂痕,你說我怎么就攤上這事≡痴牵” “怎么了握童?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)叛赚。 經(jīng)常有香客問我澡绩,道長(zhǎng),這世上最難降的妖魔是什么俺附? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任肥卡,我火速辦了婚禮,結(jié)果婚禮上事镣,老公的妹妹穿的比我還像新娘步鉴。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布唠叛。 她就那樣靜靜地躺著只嚣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艺沼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天蕴掏,我揣著相機(jī)與錄音障般,去河邊找鬼。 笑死盛杰,一個(gè)胖子當(dāng)著我的面吹牛挽荡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播即供,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼定拟,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了逗嫡?” 一聲冷哼從身側(cè)響起青自,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驱证,沒想到半個(gè)月后延窜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抹锄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年逆瑞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伙单。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡获高,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吻育,到底是詐尸還是另有隱情念秧,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布扫沼,位于F島的核電站出爹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缎除。R本人自食惡果不足惜严就,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望器罐。 院中可真熱鬧梢为,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粟害,卻和暖如春蕴忆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背悲幅。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國打工套鹅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汰具。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓卓鹿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親留荔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吟孙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容