美文网首页
一个update函数执行时间从5分钟提升到0.5s的进化之旅

一个update函数执行时间从5分钟提升到0.5s的进化之旅

作者: asseek | 来源:发表于2017-11-24 09:57 被阅读0次

接触php一年左右,系统初写的时候我没有过多的考虑性能问题,能简则简,能用一句sql解决的问题绝不多写一个标点符号的代码,于是为更新汇总子订单成本写了这个函数

    /**
     * 更新子订单成本
     * @param array $tidList 可选 要更新的tid 不传参数全量更新
     * @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);
    }

使用中因为$tidList一直都是有传参进行部分更新,每次更新条目最多不超过10条,并没察觉到有什么效率问题。
然后今天偶然来个空值全量更新,好啦,瞬间mysql锁死,整个系统处于瘫痪状态,等了足足差不多 5分钟 才把这段语句执行完。
taobao_trade_order只有不到3000条数据,taobao_sku_modify也就6000多而已,这么点数据量让系统锁死5分钟,这段代码亟待优化了。
出于我能简就简的习惯,我一上来依然是

从sql语句上着手,看看是否有提升空间。

tid,oid,status几个关键字段已经设置索引。
然后观察到这段代码执行的时候每次SET cost的时候都要执行后面的SELECT 语句,等于要执行2000多次SELECT ,我要是把这段SELECT的数据建一个临时表,然后update从这个临时表里面读数据再更新如何呢?
可是很快发现这个创建临时表的语句我 写不出来.....

同系统下另一个函数有这样的一个sql语句

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

这条语句的表现比上面那个略好,嗯....主表 taobao_trade 1800条 子表 taobao_trade_order 不到3000条的数据,执行一次 45s

这个条件简单 我先拿这条语句创建临时表并更新

CREATE TEMPORARY TABLE tmp_table (     
    SELECT tid, SUM(refund_fee) AS refund_fee FROM taobao_trade_order GROUP BY tid 
)  //创建临时表 0.02s

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

好嘛,确实有用,从45s升级到14.22s 效率提升了3倍还多,但这效率。。。依然并卵。
于是我也不再烦恼上面那个语句怎么写CREATE TEMPORARY TABLE了,这条路不通!
不过如果有人知道这种多个字段去重,并按某个字段的字段量来汇总另一个字段怎么建表希望不吝赐教。

既然mysql上面没有好的解决办法,只能考虑从

php代码上优化

通俗的讲,这段要实现的是通过库存变更表taobao_sku_modify计算每个子订单成本,然后赋值到子订单表相关联的子订单上,通过tid和oid2个字段判断关联性。
我首先把代码改成这样

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解决的原因,与原函数做一样的事情,一句sql变成了这么长一段函数,我已尽量简洁,但是看起来还是没有sql语句直观,算了,执行看看效率。
执行结果是卓有成效的,因为这次系统只卡了 86.977087020874s
从5分钟提升到差不多一分半钟,效率提升了3倍,我想如果把数据提取成临时表进行更新也差不多是这个效率了,因为它们逻辑一样。
这种效率显然不是我想要的,而且我也很快找出了问题,update函数执行了3000多次,但是执行结果都是0,也就是之前执行更新cost数据与重新计算的cost一样,并没有数据被更新,我是否可以在php层判断结果是否一致,而避免不断的做update呢,当然可以,我把函数上半部分的代码改成这样

 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)                  //优化
        {
            if (!Func::array_find($orderList, $result)) {               //通过判断orderList中是否有与result键名键值一致的数组
                DB::SlimPDO()
                    ->update(['cost' => $result['cost']])
                    ->table('taobao_trade_order')
                    ->where('tid', '=', $result['tid'])
                    ->where('oid', '=', $result['oid'])
                    ->execute();
            }
        };

执行看看,好的,有效,这次的执行时间是 29.660382032394s
还能更快吗,这效率依然不够啊,这段代码的主要时间浪费在Func::array_find的查询上,这是一个自定义的函数,用在判断参数一的二维数组是否有包含参数二的所有同键名键值的数组,参数二可以是闭包函数也可以是字符串。
我认为Func::array_find已经没有提升空间,只能找别的方法来替代,第一个想到的是in_array,可是in_array不能筛二维数组,那我把源数据直接改成一维的吧,反正只有三个字段
为了得到一维数组我把

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

改成

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

然后更新了update函数的判断方式

$_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();
            }
        };

这次我信心满满,因为已经用了系统内置的函数,应该没什么大问题了。
按下执行键的时候我快斯巴达了,还是卡住了,这次的执行时间是 30.925987958908s
跟自定义的Func::array_find判断函数比 in_array并没有带来性能的提升。
怎么办,我已经得到了差不多3000条数据的一维数组$orderList ,要让它进行差不多3000次的重复值判断,要怎么样才能把性能提升到可以接受的地步?
我搜罗了一番,找到了 iseet() 和 array_key_exists()这两个函数,并最终选则了isset
isset只能判断键名 这个好办,用array_flip把我的$orderList键值互转一下

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

然后把判断语句

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

改成

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

执行,一颗心放下了,最终执行时间 0.45861196517944s
终于到了可以接受的程度,这是最终的函数代码,以我目前技术能想到的最优方案

    /**
     * 更新子订单成本
     * @param array $tidList 可选 要更新的tid 不传参数全量更新
     */
    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);
    }

相关文章

  • 一个update函数执行时间从5分钟提升到0.5s的进化之旅

    接触php一年左右,系统初写的时候我没有过多的考虑性能问题,能简则简,能用一句sql解决的问题绝不多写一个标点符号...

  • Mysql慢日志分析

    1,慢日志配置 1)my.cnf文件long_query_time = 0.5 //执行时间超过0.5s的sql...

  • MongoDB-更新

    提供了两类函数:save()、update()update函数语法要求比较麻烦:db.集合.update(更新条件...

  • es6块级作用域定义声明函数

    允许在块级作用域内声明函数。函数声明类似于var,即会提升到全局作用域或函数作用域的头部。同时,函数声明还会提升到...

  • 引用类型

    变量提升和函数声明提升   函数声明提升就是把函数声明提升到函数声明所在作用域中(或者说一个函数体内)的顶端,变量...

  • Python装饰器20-update_wrapper

    使用update_wrapper改变装饰后函数的属性 Python还提供定制化更强的函数update_wrappe...

  • Python-装饰器

    以装饰一个函数为例子, 打印出函数的开始执行时间。 常规方式实现: 输出: 装饰器方式实现: 输出:

  • js学习总结(一)

    1.函数提升:对于函数,只有函数声明会被提升到顶部,而不包括函数表达式。 2.apply()方法调用一个函数, 其...

  • python中的装饰器

    设计一个decorator装饰器,它可作用于任何函数上,并打印该函数的执行时间: import time def ...

  • python个人学习——装饰器

    请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间: 代码: import functoo...

网友评论

      本文标题:一个update函数执行时间从5分钟提升到0.5s的进化之旅

      本文链接:https://www.haomeiwen.com/subject/bxyzvxtx.html