【问题标题】:Looping through large data array in PHP循环遍历 PHP 中的大数据数组
【发布时间】:2014-05-29 14:27:48
【问题描述】:

我有一个包含 100,000 个用户个人信息的数组(ID、姓名、电子邮件等)。我需要遍历数组的每一行,并根据行数据将 mysql 记录插入到表中。我的问题是我在大约 70,000 行后内存不足。

我的代码:

if(!empty($users)){
    $c = 0;
        foreach($users as $user){

            $message = // Some code to create custom email
            queue_mail_to_send($user->user_email, $subject, $message, $db_options, $mail_options, $mail_queue);
        }
}

背景:

我正在构建一个电子邮件系统,它会向我网站的用户发送电子邮件。上面的代码循环遍历用户数组并执行函数 'queue_mail_to_send' 将 mysql 行插入到电子邮件队列表中。 (我正在使用 PEAR 库来错开电子邮件发送)

问题:

我知道我只是在一次执行中尝试做太多事情,从而耗尽了这里的内存。那么有没有人知道更好的方法,而不是试图在一个大循环中执行所有内容?

谢谢

【问题讨论】:

  • 当然。在许多较小的循环中执行所有内容,例如每次最多 1000 个用户。
  • 如果您在$user 数组/对象中加载用户详细信息,那么为什么不直接在INSERT INTO table_name (COL1, Col2,...) SELECT COL1, COL2 FROM other_table; 等一个sql 语句中执行此操作
  • 正如@Jon 所建议的那样,在第一个循环中使用较小的循环作为限制 0,1000,然后将 num 1000 存储在临时表中,然后在下一个循环 1000,2000 等等!!
  • 我必须创建某种触发系统,一旦处理了前 1000 个脚本,它就会执行一个新的 php 脚本,并向它发送一些标头变量来跟踪它的进度以及何时完成等等。这是最简单/正确的方法吗?
  • 我可以看到弹性工作分配在这里很有用。

标签: php mysql memory memory-management


【解决方案1】:

我认为您可以使用 MySQL IN 子句,而不是为每个用户执行 foreach。

喜欢 user_ids = 数组 (1,2,3,4); // 在 user_id IN ($user_ids) 中做某事;

在发送邮件时,您可以通过在 $to 中提供逗号分隔的电子邮件地址来使用 PHPMailer 类。

【讨论】:

  • 感谢您的评论,但我怀疑这会起作用 - 因为我仍然需要执行循环来构建自定义 $message 并使用 queue_mail_to_send 添加自定义行:)
【解决方案2】:

只使用一个查询,例如:

INSERT INTO table_name (COL1, Col2,...) SELECT COL1, COL2 FROM other_table;

【讨论】:

  • 这将如何帮助他减少记忆?
  • 再一次,我怀疑这是否可行,因为我根据正在处理的检索行将唯一数据插入每一行。
  • 它将处理唯一的数据检查mysql文档。和@giorgio这将减少内存问题,因为一切都将在mysql而不是php中完成。
  • 是的,但他需要唯一的数据,基于从用户表检索到的计算,所以只有 mysql 是不够的......
  • @giorgio 我之前问过他,是的,他可以在那里过滤 SQL 语句中的用户数据。如果我遗漏了什么或者不可能这样,我想知道:)
【解决方案3】:

您可以将该操作拆分为多个操作,并在时间上分开。 例如,只允许您的例程每分钟处理 40 封电子邮件,或者可能使用数组的数组来创建记录的“页面”(使用 sql LIMIT 函数)。 当您不再需要该信息时,将数组的数组设置为 null 并取消设置。

【讨论】:

    【解决方案4】:

    我认为减少脚本的有效负载会很麻烦,并且不会给您带来令人满意的结果。如果您有任何可能这样做,我建议您记录您已经处理了哪些行,并让脚本运行接下来的 x 行。如果您可以使用 cronjob,则可以暂存邮件,并让 cronjob 每 5 分钟将邮件添加到队列中,直到处理完所有用户。

    最简单的方法是将您处理过的最高用户 ID 存储在某处。我不建议您存储用户数量,因为在批次之间可以添加或删除用户,导致用户收不到电子邮件。但是,如果您按用户 ID 进行排序(假设您对 ID 使用自动递增的列!),您可以确保每个用户都得到处理。

    所以你的用户查询会是这样的:

    SELECT * FROM users WHERE user_id > [highest_processed_user_id] ORDER BY user_id LIMIT 1000
    

    然后处理您的循环,并存储最后一个用户 ID:

    if(!empty($users)) {
        $last_processed_id = null;
        foreach($users as $user) {
            $message = // Message creation magic
            queue_mail_to_send( /** parameters **/ );
            $last_processed_id = $user->id;
        }
    
        // batch done! store processed user id
        $query = 'UPDATE mail_table SET last_processed_user_id = '. $last_processed_id; // please use parameterized statements here
        // execute the query
    }
    

    在下一次执行时,再次执行,直到所有用户都收到邮件。

    【讨论】:

    • 我认为在主循环中可能有一些垃圾收集方法可以让我在一个脚本中完成所有操作,但似乎不是,这是最可靠的方法。感谢您花时间写这篇文章。
    【解决方案5】:

    我和你有同样的问题。无论如何,@giorgio 的答案是最好的解决方案。

    但是像 java 或 python 一样,我们在 php 中有“yield”。 @see [这里] (http://php.net/manual/en/language.generators.syntax.php)

    这是我的示例代码,我的案例是 50.000 条记录。我还成功地测试了 370.000 条记录。但这需要时间。

    $items = CustomerService::findAll();
            foreach ($items AS $item)
            {
                yield (new self())->loadFromResource($item);
            }
    

    【讨论】:

    • 这也应该是公认的答案。
    猜你喜欢
    • 2023-01-28
    • 2011-05-23
    • 2012-11-14
    • 2011-04-25
    • 1970-01-01
    • 1970-01-01
    • 2019-07-10
    • 1970-01-01
    • 2011-06-11
    相关资源
    最近更新 更多