V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MrMike
V2EX  ›  PHP

针对几千条以上的数组,如何循环呢?

  •  
  •   MrMike · 2017-03-12 20:01:57 +08:00 · 3852 次点击
    这是一个创建于 2807 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我需要循环这个大数组(web 的方式),然后组合成一个字符串,再在循环外进行数据的插入工作。但是现在循环超过 5000 条时,页面就不动了,内存都用完了。有没有好的建议和解决方案,如何处理这种大数组的循环呢?

    第 1 条附言  ·  2017-03-13 10:20:52 +08:00
    经过测试,如果只是循环这几千上万条数据,代码是可以执行的,但是因为我在循环里面,需要将数据与数据库里的数据进行比对,现在 4000 条数据,就不能执行了。这个有没有办法优化呢?
    30 条回复    2017-04-22 19:55:23 +08:00
    qiayue
        1
    qiayue  
       2017-03-12 20:09:55 +08:00
    是否可以把数组放到数据库里,每次取出一些来处理呢?
    forelegance
        2
    forelegance  
       2017-03-12 20:17:17 +08:00
    需要一下子都读取完,可以每读 3000 条缓存一下当前的 sumary 再继续,关键组成一个字符串显示出来???? 不会有人这么弄的吧
    MrMike
        3
    MrMike  
    OP
       2017-03-12 20:17:20 +08:00
    @qiayue 我就是要把数组里的数据插入到数据库的。。。
    forelegance
        4
    forelegance  
       2017-03-12 20:17:50 +08:00
    你是读取基因组或者转录组数据嘛???
    MrMike
        5
    MrMike  
    OP
       2017-03-12 20:21:49 +08:00
    @forelegance 咋个缓存?我就是想这样,固定循环多条数据,循环一部分,然后再将余下的数组生成一个新的数组再循环,但是现在还是要死掉。
    loading
        6
    loading  
       2017-03-12 20:25:24 +08:00 via Android
    是在浏览器跑吗?你的应该是运算量太大,页面受影响了,看看 worker 。
    随便找的一个:
    http://www.cnblogs.com/feng_013/archive/2011/09/20/2175007.html
    Ouyangan
        7
    Ouyangan  
       2017-03-12 21:26:53 +08:00
    换个思路 , 能不能提交文件,让后端来处理数据呢
    iyaozhen
        8
    iyaozhen  
       2017-03-12 22:23:59 +08:00 via Android
    5000 多条也很快吧。先考虑优化一下循环。和数据库插入。话说代码呢?

    或者异步处理,用户提交数据后马上返回成功(ob_end_flush),然后 PHP-fpm 其实还在运行。还比较耗时的话做成任务队列
    AbrahamGreyson
        9
    AbrahamGreyson  
       2017-03-12 22:30:05 +08:00
    通常这种需求要实现生成器,也就是 yeild

    自己看吧

    http://php.net/manual/zh/language.generators.overview.php
    usedname
        10
    usedname  
       2017-03-12 22:38:58 +08:00
    使用 yield ,同 9 楼
    MrMike
        11
    MrMike  
    OP
       2017-03-12 22:56:58 +08:00
    @iyaozhen 代码这里:
    代码:
    $total = count($importData);
    $page = intval(ceil($total / $pageNumber));
    for ($p = 0; $p < $page; $p++)
    {
    $start = $p * $pageNumber;
    $sliceData = array_slice($importData, $start, $pageNumber, true);
    $productSQLValue = false;
    foreach ($sliceData as $fieldValue)
    {
    dump($fieldValue);
    // if (!$fieldValue['modelName'] || !$fieldValue['type'] || !$fieldValue['category'])
    // continue;
    // $categoryID = $this->getProductCategoryID($fieldValue['category']);
    // $typeID = $this->getProductTypeID($fieldValue['type']);
    // if (!$categoryID || !$typeID)
    // continue;
    // $model = $this->getModel($fieldValue['brandName'], $fieldValue['modelName']);
    // $resourceModel = $model['resourceModel'];
    // if ($resourceModel)
    // {
    // $resourceModel = $resourceModel->getId();
    // }
    // $product = $this->entityManager->getRepository('DemoProductBundle:Product')->findOneBy(array('creator' => $productCreator, 'model' => $model['productModel']));
    // $row = array();
    // if ($product)
    // {
    // $row['id'] = $product->getId();
    // $row['sn'] = $product->getSn();
    // $row['created'] = $product->getCreated()->format('Y-m-d H:i:s');
    // } else {
    // $row['id'] = $latestProductID + $pID;
    // $row['sn'] = $this->redis->getNumber('product-sku');
    // $row['created'] = date('Y-m-d H:i:s');
    // $pID++;
    // }
    // $row['title'] = $fieldValue['title'];
    // $row['excerpt'] = $fieldValue['excerpt'];
    // $row['description'] = $fieldValue['description'];
    // $row['city'] = $this->getProductCityID($fieldValue['originPlace']);
    // $row['model'] = $model['productModel']->getId();
    // $row['resource_model'] = $resourceModel;
    // $row['category'] = $categoryID;
    // $row['type'] = $typeID;
    // $row['retail_price'] = $fieldValue['retailPrice'];
    // $row['status'] = 1;
    // $row['modified'] = date('Y-m-d H:i:s');
    // dump($row);
    // $productSQLValue .= "('". implode("','", $row) . "'),";
    }
    dump($p);
    // dump($productSQLValue);
    // dump($sliceData);
    dump(date('H:i:s'));

    // if ($p > 3)
    // break;
    }

    输出 dump($fieldValue);可以工作
    // dump($productSQLValue);如果想输出组合后的 sql,就挂掉了。
    MrMike
        12
    MrMike  
    OP
       2017-03-12 22:58:24 +08:00
    @AbrahamGreyson 之前用过这个,感觉数据不是太多,时间上没有节省多少。现在看来,是循环内,对数据库查询造成的问题了。我已经把代码贴在 11 楼了。
    MrMike
        13
    MrMike  
    OP
       2017-03-12 23:00:13 +08:00
    @Ouyangan 我想让 cron 来自动执行,但是苦于自己对 shell 脚本不熟,只好暂时不花时间在这个上面了。
    fork3rt
        14
    fork3rt  
       2017-03-13 07:51:46 +08:00 via Android
    你需要 yield
    MrMike
        15
    MrMike  
    OP
       2017-03-13 08:55:04 +08:00
    @fork3rt 现在貌似问题不是出在循环的问题上了,是我循环里面,需要去查询数据库,我用的是 doctrine 的 ORM 类去读取数据库,速度好像跟原生的 sql 查询有点不一样,不晓得是不是这个原因造成的。
    MrMike
        16
    MrMike  
    OP
       2017-03-13 08:55:26 +08:00
    @fork3rt 之前用 yield 来测试过的。
    harker
        17
    harker  
       2017-03-13 09:24:13 +08:00
    数组值作为变量赋值,源数组申请到的内存是不会释放的,而且会成倍叠加申请内存,一步步测试,看看内存从哪里消耗掉的
    herozhang
        18
    herozhang  
       2017-03-13 09:41:40 +08:00
    听上去 lz 是在页面上直接 js 来做的吧?调试看下内存分配的情况吧
    eoo
        19
    eoo  
       2017-03-13 09:41:40 +08:00 via Android
    使用 yield
    MrMike
        20
    MrMike  
    OP
       2017-03-13 09:42:48 +08:00
    @herozhang 不是用 js 调用的,直接通过页面提交,然后后台代码执行的
    herozhang
        21
    herozhang  
       2017-03-13 10:05:51 +08:00
    @MrMike 代码里面尝试把不用的字符串变量 unset 一下?
    MrMike
        22
    MrMike  
    OP
       2017-03-13 10:19:07 +08:00
    @herozhang 都是要用的字符串,没有不用的哦。我现在发现,可能是因为在循环里面有数据库的查询的问题。但是又必须要针对数据库进行比对,这个有没有好的解决方案?
    Immortal
        23
    Immortal  
       2017-03-13 10:29:43 +08:00
    每次遇到这类问题我总觉得是架构上不合理..
    zhs227
        24
    zhs227  
       2017-03-13 10:32:42 +08:00
    php 里有一种叫做 generator ,应该可以解决这个问题。

    ```
    生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。

    生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。
    ```
    MrMike
        25
    MrMike  
    OP
       2017-03-13 10:49:38 +08:00
    @Immortal 数据库结构是:
    主表:product ,属性表: product_attribute(product_id 为外链),产品图片表: product_image(product_id 为外链),产品价格表: product_price(product_id 为外链),产品型号表: product_model ,型号表跟主表(product)是一对多的关系,同一个型号,可能因为型号里面的品牌不同,会有不同的产品,所以在主表 product 里面有一个 model_id 字段关联 product_model 表,主表 product 里面还有一个所有者 creator_id 字段,关联当前产品的所有者。
    在循环中,需要根据传递的所有者 ID 和型号名称,去查询当前产品是否已经存在数据库中了,同一个用户只能上传一个相同型号的产品。

    不知道这样的结构,有没有问题呢?谢谢哈。
    mrgeneral
        26
    mrgeneral  
       2017-03-13 11:03:27 +08:00
    yield 是一种方法。

    另外就是优化逻辑啊,你无脑一个循环查询,先从数组中把查询条件提取出来,一次性查询完,然后再用结果和原数组进行逻辑处理,最后得到的结果再插入到数据库。

    两次数据库操作,几千条数组 PHP 处理其实是很快的,关于内存,只选择必要数据就好了。

    如果必要数据都超过限制,就不要一次性查询完,用 yield 分批查询,也比无脑循环查询好。
    AbrahamGreyson
        27
    AbrahamGreyson  
       2017-03-13 11:03:48 +08:00 via iPhone
    @MrMike 具体代码没看,五千次循环查询,建议用队列。
    notgod
        28
    notgod  
       2017-03-13 11:59:13 +08:00   ❤️ 1
    看不下去了, 这个问题能在首页 2 天.....
    这个不能算问题的少年 从头到尾都是个逻辑问题

    你把详细需求列出来 一个一个解决

    #1 预期 超 1W+的循环, 这个循环根本不是问题, 无论是使用 yield 还是自循环

    #2. 1 数据库查询瓶颈 就是检查数据库的重复记录, 这个可以使用 Redis/Memcache 缓存
    先把 mysql 的需要比较重复的字段 读出来, 需要比较的写到 KVDB 里, 后期每次检查 /更新+新增加到 KVDB
    如果闲 KVDB 麻烦, 别人封装好了 www.phpfastcache.com , 可以 file 可以 sqlite

    #2. 2 MYSQL 建好索引 /使用 IN 进行查询 性能开销没你想的那么大

    #3 插入瓶颈
    这个解决办法不是很多? 你换个逻辑 为什么一定要循环后直接写入? 而且还是单条插次
    不能按 sql 的格式 写到本地文本文件?
    先在#2 的步骤 使用 if 去判断是不是重复 如果不重复 生成个格式 写到本地文件里
    然后 批量导入 使用 mysql 的批量导入命令
    可以使用 shell_exec 调用 mysql 的 cli 运行, 也可以直接插入
    本来一次插一条 这个过程都需要和 mysql 连接一次
    如果你一次性插 1000 条 也是连接一次
    一般 mysql 连接数设的都是 100-500
    而且很多人没关闭连接的习惯.....

    你的问题在于优化业务逻辑, 不是代码 不是代码 不是代码
    MrMike
        29
    MrMike  
    OP
       2017-03-13 12:19:40 +08:00
    @notgod 非常感谢。项目本身就已经用了 redis 来缓存,只是 redis 和项目在同一个服务器上,因为对它不熟悉,在其他功能上, redis 经常会出错,所以一直没想把数据全部缓存到 redis 上面。我再试试。
    mingyun
        30
    mingyun  
       2017-04-22 19:55:23 +08:00
    cli 执行 执行完一次 sleep 下
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5827 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 37ms · UTC 01:50 · PVG 09:50 · LAX 17:50 · JFK 20:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.