一個(gè)update函數(shù)執(zhí)行時(shí)間從5分鐘提升到0.5s的進(jìn)化之旅

接觸php一年左右河咽,系統(tǒng)初寫(xiě)的時(shí)候我沒(méi)有過(guò)多的考慮性能問(wèn)題,能簡(jiǎn)則簡(jiǎn),能用一句sql解決的問(wèn)題絕不多寫(xiě)一個(gè)標(biāo)點(diǎn)符號(hào)的代碼耀石,于是為更新匯總子訂單成本寫(xiě)了這個(gè)函數(shù)

    /**
     * 更新子訂單成本
     * @param array $tidList 可選 要更新的tid 不傳參數(shù)全量更新
     * @return int
     */
    public function updateOrderCost(array $tidList = [])
    {
        $sql = "UPDATE taobao_trade_order a SET a.cost = IFNULL((SELECT SUM(cost) FROM taobao_sku_modify WHERE tid = a.tid AND oid = a.oid AND status = 1),0)";
        if (count($tidList)) {
            $tid = implode(',', $tidList);
            $sql .= " WHERE tid in ({$tid})";
        }
        return DB::SlimPDO()->exec($sql);
    }

使用中因?yàn)?tidList一直都是有傳參進(jìn)行部分更新,每次更新條目最多不超過(guò)10條爸黄,并沒(méi)察覺(jué)到有什么效率問(wèn)題滞伟。
然后今天偶然來(lái)個(gè)空值全量更新,好啦炕贵,瞬間mysql鎖死梆奈,整個(gè)系統(tǒng)處于癱瘓狀態(tài),等了足足差不多 5分鐘 才把這段語(yǔ)句執(zhí)行完称开。
taobao_trade_order只有不到3000條數(shù)據(jù)亩钟,taobao_sku_modify也就6000多而已,這么點(diǎn)數(shù)據(jù)量讓系統(tǒng)鎖死5分鐘鳖轰,這段代碼亟待優(yōu)化了清酥。
出于我能簡(jiǎn)就簡(jiǎn)的習(xí)慣,我一上來(lái)依然是

從sql語(yǔ)句上著手蕴侣,看看是否有提升空間焰轻。

tid,oid,status幾個(gè)關(guān)鍵字段已經(jīng)設(shè)置索引。
然后觀察到這段代碼執(zhí)行的時(shí)候每次SET cost的時(shí)候都要執(zhí)行后面的SELECT 語(yǔ)句昆雀,等于要執(zhí)行2000多次SELECT 鹦马,我要是把這段SELECT的數(shù)據(jù)建一個(gè)臨時(shí)表,然后update從這個(gè)臨時(shí)表里面讀數(shù)據(jù)再更新如何呢忆肾?
可是很快發(fā)現(xiàn)這個(gè)創(chuàng)建臨時(shí)表的語(yǔ)句我 寫(xiě)不出來(lái).....

同系統(tǒng)下另一個(gè)函數(shù)有這樣的一個(gè)sql語(yǔ)句

UPDATE taobao_trade a SET a.refund_fee = (SELECT SUM(refund_fee) FROM taobao_trade_order WHERE tid = a.tid) //45s

這條語(yǔ)句的表現(xiàn)比上面那個(gè)略好荸频,嗯....主表 taobao_trade 1800條 子表 taobao_trade_order 不到3000條的數(shù)據(jù),執(zhí)行一次 45s

這個(gè)條件簡(jiǎn)單 我先拿這條語(yǔ)句創(chuàng)建臨時(shí)表并更新

CREATE TEMPORARY TABLE tmp_table (     
    SELECT tid, SUM(refund_fee) AS refund_fee FROM taobao_trade_order GROUP BY tid 
)  //創(chuàng)建臨時(shí)表 0.02s

UPDATE taobao_trade a SET a.refund_fee = (SELECT refund_fee FROM tmp_table WHERE tid = a.tid) //執(zhí)行更新 14.22s

好嘛客冈,確實(shí)有用旭从,從45s升級(jí)到14.22s 效率提升了3倍還多,但這效率。和悦。退疫。依然并卵。
于是我也不再煩惱上面那個(gè)語(yǔ)句怎么寫(xiě)CREATE TEMPORARY TABLE了鸽素,這條路不通褒繁!
不過(guò)如果有人知道這種多個(gè)字段去重,并按某個(gè)字段的字段量來(lái)匯總另一個(gè)字段怎么建表希望不吝賜教馍忽。

既然mysql上面沒(méi)有好的解決辦法棒坏,只能考慮從

php代碼上優(yōu)化

通俗的講,這段要實(shí)現(xiàn)的是通過(guò)庫(kù)存變更表taobao_sku_modify計(jì)算每個(gè)子訂單成本遭笋,然后賦值到子訂單表相關(guān)聯(lián)的子訂單上坝冕,通過(guò)tid和oid2個(gè)字段判斷關(guān)聯(lián)性。
我首先把代碼改成這樣

public function updateOrderCost(array $tidList = [])
    {
        $search = DB::SlimPDO()
            ->select(['tid', 'oid', 'cost'])
            ->from('taobao_sku_modify')
            ->where('status', '=', 1)
            ->where('oid', '<>', '');

        if (count($tidList)) {
            $search->whereIn('tid', $tidList);
        }

        $list = $search->orderBy('tid')->execute()->fetchAll();

        function update($result)
        {
            DB::SlimPDO()
                ->update(['cost' => $result['cost']])
                ->table('taobao_trade_order')
                ->where('tid', '=', $result['tid'])
                ->where('oid', '=', $result['oid'])
                ->execute();
        }

        $result = [
            'tid' => '',
            'oid' => '',
            'cost' => 0
        ];

        foreach ($list as $item) {
            if ($result['tid'] !== $item['tid']) {
                if ($result['tid']) {
                    update($result);
                }
                $result = [
                    'tid' => $item['tid'],
                    'oid' => $item['oid'],
                    'cost' => 0
                ];
            } elseif ($result['oid'] !== $item['oid']) {
                update($result);
                $result['oid'] = $item['oid'];
                $result['cost'] = 0;
            }
            $result['cost'] += $item['cost'];
        }
        update($result);
    }

這就是我更愿意一句sql解決的原因瓦呼,與原函數(shù)做一樣的事情喂窟,一句sql變成了這么長(zhǎng)一段函數(shù),我已盡量簡(jiǎn)潔央串,但是看起來(lái)還是沒(méi)有sql語(yǔ)句直觀磨澡,算了,執(zhí)行看看效率质和。
執(zhí)行結(jié)果是卓有成效的钱贯,因?yàn)檫@次系統(tǒng)只卡了 86.977087020874s
從5分鐘提升到差不多一分半鐘,效率提升了3倍侦另,我想如果把數(shù)據(jù)提取成臨時(shí)表進(jìn)行更新也差不多是這個(gè)效率了秩命,因?yàn)樗鼈冞壿嬕粯印?br> 這種效率顯然不是我想要的,而且我也很快找出了問(wèn)題褒傅,update函數(shù)執(zhí)行了3000多次弃锐,但是執(zhí)行結(jié)果都是0,也就是之前執(zhí)行更新cost數(shù)據(jù)與重新計(jì)算的cost一樣殿托,并沒(méi)有數(shù)據(jù)被更新霹菊,我是否可以在php層判斷結(jié)果是否一致,而避免不斷的做update呢支竹,當(dāng)然可以旋廷,我把函數(shù)上半部分的代碼改成這樣

 public function updateOrderCost(array $tidList = [])
    {
        $search = DB::SlimPDO()
            ->select(['tid', 'oid', 'cost'])
            ->from('taobao_sku_modify')
            ->where('status', '=', 1)
            ->where('oid', '<>', '');

        $sql = "SELECT cost, tid, oid FROM taobao_trade_order";         //新增

        if (count($tidList)) {
            $search->whereIn('tid', $tidList);
            $tid = implode(',', $tidList);
            $sql .= " WHERE tid in ({$tid})";                           //新增
        }

        $list = $search->orderBy('tid')->execute()->fetchAll();

        $orderList = DB::SlimPDO()->query($sql)->fetchAll();            //新增

        $_update = function ($result) use ($orderList)                  //優(yōu)化
        {
            if (!Func::array_find($orderList, $result)) {               //通過(guò)判斷orderList中是否有與result鍵名鍵值一致的數(shù)組
                DB::SlimPDO()
                    ->update(['cost' => $result['cost']])
                    ->table('taobao_trade_order')
                    ->where('tid', '=', $result['tid'])
                    ->where('oid', '=', $result['oid'])
                    ->execute();
            }
        };

執(zhí)行看看,好的礼搁,有效饶碘,這次的執(zhí)行時(shí)間是 29.660382032394s
還能更快嗎,這效率依然不夠啊馒吴,這段代碼的主要時(shí)間浪費(fèi)在Func::array_find的查詢(xún)上扎运,這是一個(gè)自定義的函數(shù)瑟曲,用在判斷參數(shù)一的二維數(shù)組是否有包含參數(shù)二的所有同鍵名鍵值的數(shù)組,參數(shù)二可以是閉包函數(shù)也可以是字符串豪治。
我認(rèn)為Func::array_find已經(jīng)沒(méi)有提升空間洞拨,只能找別的方法來(lái)替代,第一個(gè)想到的是in_array负拟,可是in_array不能篩二維數(shù)組烦衣,那我把源數(shù)據(jù)直接改成一維的吧,反正只有三個(gè)字段
為了得到一維數(shù)組我把

$orderList = DB::SlimPDO()->query($sql)->fetchAll();

改成

$orderList = array_map(function ($item) {
            return $item['tid'] . $item['oid'] . $item['cost'];
        }, DB::SlimPDO()->query($sql)->fetchAll());

然后更新了update函數(shù)的判斷方式

$_update = function ($result) use ($orderList)    
        {
            $test = $result['tid'] . $result['oid'] . $result['cost'];
            if (!in_array($test, $orderList)) {
                DB::SlimPDO()
                    ->update(['cost' => $result['cost']])
                    ->table('taobao_trade_order')
                    ->where('tid', '=', $result['tid'])
                    ->where('oid', '=', $result['oid'])
                    ->execute();
            }
        };

這次我信心滿(mǎn)滿(mǎn)掩浙,因?yàn)橐呀?jīng)用了系統(tǒng)內(nèi)置的函數(shù)花吟,應(yīng)該沒(méi)什么大問(wèn)題了。
按下執(zhí)行鍵的時(shí)候我快斯巴達(dá)了涣脚,還是卡住了,這次的執(zhí)行時(shí)間是 30.925987958908s
跟自定義的Func::array_find判斷函數(shù)比 in_array并沒(méi)有帶來(lái)性能的提升寥茫。
怎么辦遣蚀,我已經(jīng)得到了差不多3000條數(shù)據(jù)的一維數(shù)組$orderList ,要讓它進(jìn)行差不多3000次的重復(fù)值判斷纱耻,要怎么樣才能把性能提升到可以接受的地步芭梯?
我搜羅了一番,找到了 iseet() 和 array_key_exists()這兩個(gè)函數(shù)弄喘,并最終選則了isset
isset只能判斷鍵名 這個(gè)好辦玖喘,用array_flip把我的$orderList鍵值互轉(zhuǎn)一下

$orderList = array_flip(array_map(function ($item) {
            return $item['tid'] . $item['oid'] . $item['cost'];
        }, DB::SlimPDO()->query($sql)->fetchAll()));

然后把判斷語(yǔ)句

if (!in_array($test, $orderList)) 

改成

if (!isset($orderList[$test])) 

執(zhí)行,一顆心放下了蘑志,最終執(zhí)行時(shí)間 0.45861196517944s
終于到了可以接受的程度累奈,這是最終的函數(shù)代碼,以我目前技術(shù)能想到的最優(yōu)方案

    /**
     * 更新子訂單成本
     * @param array $tidList 可選 要更新的tid 不傳參數(shù)全量更新
     */
    public function updateOrderCost(array $tidList = [])
    {
        $search = DB::SlimPDO()
            ->select(['tid', 'oid', 'cost'])
            ->from('taobao_sku_modify')
            ->where('status', '=', 1)
            ->where('oid', '<>', '');

        $sql = "SELECT cost, tid, oid FROM taobao_trade_order";

        if (count($tidList)) {
            $search->whereIn('tid', $tidList);
            $tid = implode(',', $tidList);
            $sql .= " WHERE tid in ({$tid})";
        }

        $list = $search->orderBy('tid')->execute()->fetchAll();

        $orderList = array_flip(array_map(function ($item) {
            return $item['tid'] . $item['oid'] . $item['cost'];
        }, DB::SlimPDO()->query($sql)->fetchAll()));

        $Update = function ($result) use ($orderList) {
            $test = $result['tid'] . $result['oid'] . $result['cost'];
            if (!isset($orderList[$test])) {
                DB::SlimPDO()
                    ->update(['cost' => $result['cost']])
                    ->table('taobao_trade_order')
                    ->where('tid', '=', $result['tid'])
                    ->where('oid', '=', $result['oid'])
                    ->execute();
            }
        };

        $result = [
            'tid' => '',
            'oid' => '',
            'cost' => 0
        ];

        foreach ($list as $item) {
            if ($result['tid'] !== $item['tid']) {
                if ($result['tid']) {
                    $Update($result);
                }
                $result = [
                    'tid' => $item['tid'],
                    'oid' => $item['oid'],
                    'cost' => 0
                ];
            } elseif ($result['oid'] !== $item['oid']) {
                $Update($result);
                $result['oid'] = $item['oid'];
                $result['cost'] = 0;
            }
            $result['cost'] += $item['cost'];
        }
        $Update($result);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末急但,一起剝皮案震驚了整個(gè)濱河市澎媒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌波桩,老刑警劉巖戒努,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異镐躲,居然都是意外死亡储玫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)萤皂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撒穷,“玉大人,你說(shuō)我怎么就攤上這事裆熙∏疟酰” “怎么了窝爪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)齐媒。 經(jīng)常有香客問(wèn)我蒲每,道長(zhǎng),這世上最難降的妖魔是什么喻括? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任邀杏,我火速辦了婚禮,結(jié)果婚禮上唬血,老公的妹妹穿的比我還像新娘望蜡。我一直安慰自己,他們只是感情好拷恨,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布脖律。 她就那樣靜靜地躺著,像睡著了一般腕侄。 火紅的嫁衣襯著肌膚如雪小泉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,096評(píng)論 1 291
  • 那天冕杠,我揣著相機(jī)與錄音微姊,去河邊找鬼。 笑死分预,一個(gè)胖子當(dāng)著我的面吹牛兢交,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播笼痹,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼配喳,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了凳干?” 一聲冷哼從身側(cè)響起界逛,我...
    開(kāi)封第一講書(shū)人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纺座,沒(méi)想到半個(gè)月后息拜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡净响,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年少欺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馋贤。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赞别,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出配乓,到底是詐尸還是另有隱情仿滔,我是刑警寧澤惠毁,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站崎页,受9級(jí)特大地震影響鞠绰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜飒焦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一蜈膨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧牺荠,春花似錦翁巍、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至杈曲,卻和暖如春驰凛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鱼蝉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工洒嗤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留箫荡,地道東北人魁亦。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像羔挡,于是被迫代替她去往敵國(guó)和親洁奈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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