接觸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);
}