【问题标题】:Processing huge files with php into a database用php处理大文件到数据库
【发布时间】:2014-05-30 15:03:10
【问题描述】:

我有一个包含 150,000 行的文本文件(本质上是一个没有扩展名的 csv)。我需要按键删除重复项,然后将它们插入数据库。我正在尝试 fgetcvs 逐行读取它,但我不想进行 150,000 次查询。所以这就是我到目前为止想出的:(请记住我正在使用 laravel)

    $count = 0;
    $insert = [];

    if (($handle = fopen("myHUGEfile.txt", "r")) !== FALSE) {
        while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
            $count++;

            //See if this is the top row, which in this case are column headers
            if ($count == 1) continue;

            //Get the parts needed for the new part
            $quantity = $data[0];
            $part_number = $data[1];
            $manufacturer = $data[2];

            $new_part = [
                'manufacturer' => $manufacturer,
                'part_number' => $part_number,
                'stock' => $quantity,
                'price' => '[]',
                'approved' => 0,
            ];

            $insert[] = $new_part;

        }
        fclose($handle);
    } else {
        throw new Exception('Could not open file for reading.');
    }

    //Remove duplicates
    $newRows = [];
    $parsedCount = 0;
    foreach ($insert as $row) {
        $x = 0;
        foreach ($newRows as $n) {
            if (strtoupper($row['part_number']) === strtoupper($n['part_number'])) {
                $x++;
            }
        }
        if ($x == 0) {
            $parsedCount++;
            $newRows[] = $row;
        }
    }
    $parsed_rows = array_chunk($newRows, 1000, true);

    $x = 0;
    foreach ($parsed_rows as $chunk) {
        //Insert
        if (count($chunk) > 0)
            if (DB::table('search_parts')->insert($chunk))
                $x++;
    }

    echo $x . " chunks inserted.<br/>" . $count . " parts started with<br/>" . $parsedCount . " rows after duplicates removed.";

但它非常笨拙,我只测试了超过 1000 行,它可以使用 localhost。但我担心如果我将它推向生产,它将无法处理所有 150,000 行。该文件大约 4mb。

有人可以告诉我一个更好更有效的方法吗?

【问题讨论】:

  • 为什么不将文件放入临时数据库,然后在那里进行重复消除?一次性处理整个数据集比每行更容易处理“这已经存在了吗?”查询。
  • 在 LOAD DATA INFILE 中酌情使用 REPLACE/IGNORE,就不会出现此类问题

标签: php mysql file-io laravel


【解决方案1】:

现在,您正在保留第一个重复记录。如果你可以保持 last 的欺骗,你可以改变

 $insert[] = $new_part;

$insert[strtoupper($part_number)] = $new_part

这样,您的$insert 数组对于每个$part_number 将只有一个值。您的插入会慢一点,但您可以删除所有检查重复项的代码,这些代码看起来非常非常慢。

【讨论】:

  • 这实际上帮助很大。它消除了笨重的 foreach 几秒钟。我最终删除了分块并将其作为一个查询插入,它作为一个 1000 的小文件工作,所以我想这取决于完整文件的服务器功能。
【解决方案2】:

4Mb 远不是一个“巨大”的文件。我只是将整个内容读入一个由零件号键入的 assoc 数组,该数组本质上会进行重复数据删除,每当遇到重复项时都会为您提供最后一行。可能是这样的:

$parts = [];
foreach (explode("\n", file_get_contents('file')) as $line) {
    $part = str_getcsv($line);
    $parts[$part[1]] = [
        'manufacturer' => $part[2],
        'part_number' => $part[1],
        'stock' => $part[0],
        'price' => '[]',
        'approved' => 0,
    ];
}
// $parts now contains unique part list
foreach ($parts as $part) {
    $db->insert($part);
}

【讨论】:

    【解决方案3】:

    如果您不希望在某个或多个键上出现重复,您可以自己轻松操作,只需在您不想在桌面上重复的键上添加 UNIQUE INDEX

    这样,您只需担心处理文件。当它到达重复键时,它将无法插入并继续。

    这也将使将来变得更容易,因为如果您需要对其他列进行检查,则不必修改代码。只需修改索引即可。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-06-05
      • 2010-09-30
      • 1970-01-01
      • 1970-01-01
      • 2019-11-09
      • 2010-09-15
      • 1970-01-01
      相关资源
      最近更新 更多