我需要循环这个大数组(web 的方式),然后组合成一个字符串,再在循环外进行数据的插入工作。但是现在循环超过 5000 条时,页面就不动了,内存都用完了。有没有好的建议和解决方案,如何处理这种大数组的循环呢?
1
qiayue 2017-03-12 20:09:55 +08:00
是否可以把数组放到数据库里,每次取出一些来处理呢?
|
2
forelegance 2017-03-12 20:17:17 +08:00
需要一下子都读取完,可以每读 3000 条缓存一下当前的 sumary 再继续,关键组成一个字符串显示出来???? 不会有人这么弄的吧
|
4
forelegance 2017-03-12 20:17:50 +08:00
你是读取基因组或者转录组数据嘛???
|
5
MrMike OP @forelegance 咋个缓存?我就是想这样,固定循环多条数据,循环一部分,然后再将余下的数组生成一个新的数组再循环,但是现在还是要死掉。
|
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 |
7
Ouyangan 2017-03-12 21:26:53 +08:00
换个思路 , 能不能提交文件,让后端来处理数据呢
|
8
iyaozhen 2017-03-12 22:23:59 +08:00 via Android
5000 多条也很快吧。先考虑优化一下循环。和数据库插入。话说代码呢?
或者异步处理,用户提交数据后马上返回成功(ob_end_flush),然后 PHP-fpm 其实还在运行。还比较耗时的话做成任务队列 |
9
AbrahamGreyson 2017-03-12 22:30:05 +08:00
|
10
usedname 2017-03-12 22:38:58 +08:00
使用 yield ,同 9 楼
|
11
MrMike OP @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,就挂掉了。 |
12
MrMike OP @AbrahamGreyson 之前用过这个,感觉数据不是太多,时间上没有节省多少。现在看来,是循环内,对数据库查询造成的问题了。我已经把代码贴在 11 楼了。
|
14
fork3rt 2017-03-13 07:51:46 +08:00 via Android
你需要 yield
|
15
MrMike OP @fork3rt 现在貌似问题不是出在循环的问题上了,是我循环里面,需要去查询数据库,我用的是 doctrine 的 ORM 类去读取数据库,速度好像跟原生的 sql 查询有点不一样,不晓得是不是这个原因造成的。
|
17
harker 2017-03-13 09:24:13 +08:00
数组值作为变量赋值,源数组申请到的内存是不会释放的,而且会成倍叠加申请内存,一步步测试,看看内存从哪里消耗掉的
|
18
herozhang 2017-03-13 09:41:40 +08:00
听上去 lz 是在页面上直接 js 来做的吧?调试看下内存分配的情况吧
|
19
eoo 2017-03-13 09:41:40 +08:00 via Android
使用 yield
|
22
MrMike OP @herozhang 都是要用的字符串,没有不用的哦。我现在发现,可能是因为在循环里面有数据库的查询的问题。但是又必须要针对数据库进行比对,这个有没有好的解决方案?
|
23
Immortal 2017-03-13 10:29:43 +08:00
每次遇到这类问题我总觉得是架构上不合理..
|
24
zhs227 2017-03-13 10:32:42 +08:00
php 里有一种叫做 generator ,应该可以解决这个问题。
``` 生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。 生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。 ``` |
25
MrMike OP @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 和型号名称,去查询当前产品是否已经存在数据库中了,同一个用户只能上传一个相同型号的产品。 不知道这样的结构,有没有问题呢?谢谢哈。 |
26
mrgeneral 2017-03-13 11:03:27 +08:00
yield 是一种方法。
另外就是优化逻辑啊,你无脑一个循环查询,先从数组中把查询条件提取出来,一次性查询完,然后再用结果和原数组进行逻辑处理,最后得到的结果再插入到数据库。 两次数据库操作,几千条数组 PHP 处理其实是很快的,关于内存,只选择必要数据就好了。 如果必要数据都超过限制,就不要一次性查询完,用 yield 分批查询,也比无脑循环查询好。 |
27
AbrahamGreyson 2017-03-13 11:03:48 +08:00 via iPhone
@MrMike 具体代码没看,五千次循环查询,建议用队列。
|
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 而且很多人没关闭连接的习惯..... 你的问题在于优化业务逻辑, 不是代码 不是代码 不是代码 |
29
MrMike OP @notgod 非常感谢。项目本身就已经用了 redis 来缓存,只是 redis 和项目在同一个服务器上,因为对它不熟悉,在其他功能上, redis 经常会出错,所以一直没想把数据全部缓存到 redis 上面。我再试试。
|
30
mingyun 2017-04-22 19:55:23 +08:00
cli 执行 执行完一次 sleep 下
|