导入大型CSV文件的最佳做法

时间:2010-11-12 16:01:44

标签: csv import

我的公司每月都会获得一组包含银行帐户信息的CSV文件,我需要将其导入数据库。其中一些文件可能非常大。例如,一个是大约33MB和大约65,000行。

现在我有一个symfony / Doctrine应用程序(PHP),它读取这些CSV文件并将它们导入数据库。我的数据库有大约35个不同的表,在导入过程中,我将这些行拆分为组成对象并将它们插入到数据库中。这一切都很漂亮,除了(每行大约需要四分之一秒)并且它使用了大量内存。

内存使用非常糟糕,我必须拆分我的CSV文件。一个20,000行的文件几乎没有进入。当它接近结束时,我的内存使用率为95%。导入该65,000行文件根本不可能。

我发现symfony是构建应用程序的特殊框架,我通常不会考虑使用其他任何东西,但在这种情况下,我愿意以性能的名义将所有的先入之见抛弃窗外。我不承诺任何特定语言,DBMS或任何其他内容。

Stack Overflow不喜欢主观问题所以我会尝试尽可能不主观:对于那些你不仅仅有意见但经验导入大型CSV文件的人 ,您过去使用过什么工具/做法哪些成功了?

例如,您是否只使用Django的ORM / OOP并且您没有遇到任何问题?或者您是否将整个CSV文件读入内存并准备了一些大量的INSERT语句?

同样,我不仅仅是一个意见,而是一些过去实际上对你有用的东西。

编辑:我不只是将85列CSV电子表格导入一个85列数据库表。我正在将数据规范化并将其放入几十个不同的表中。出于这个原因,我不能只使用LOAD DATA INFILE(我正在使用MySQL)或任何其他只读取CSV文件的DBMS功能。

另外,我不能使用任何特定于Microsoft的解决方案。

10 个答案:

答案 0 :(得分:17)

如果我没有正确理解您的问题,请原谅我,但似乎您只是想将大量CSV数据存入SQL数据库。您是否有任何理由要使用Web应用程序或其他代码将CSV数据处理为INSERT语句?我已成功使用SQL Server Management Studio和使用BULK INSERT语句将大量CSV数据导入SQL Server Express(免费版)。一个简单的批量插入看起来像这样:

BULK INSERT [Company].[Transactions]
    FROM "C:\Bank Files\TransactionLog.csv"
    WITH
    (
        FIELDTERMINATOR = '|',
        ROWTERMINATOR = '\n',
        MAXERRORS = 0,
        DATAFILETYPE = 'widechar',
        KEEPIDENTITY
    )
GO

答案 1 :(得分:11)

大约两周前,我遇到了同样的问题。我写了一些.NET来做ROW BY ROW插入,根据我的计算得到的数据量,用这种方式花费大约一个星期。

因此,我使用字符串构建器创建一个巨大的查询并将其一次性发送到我的关系系统。它从花了一个星期到花了5分钟。现在我不知道你正在使用什么样的关系系统,但是如果有大量的查询,你可能需要调整你的max_allowed_pa​​cket参数或类似的。

答案 2 :(得分:5)

首先:33MB 大。 MySQL可以轻松处理这种大小的数据。

正如您所注意到的,逐行插入很慢。在其上使用ORM甚至更慢:构建对象,序列化等的开销很大。使用ORM在35个表中执行此操作甚至更慢。不要这样做。

你确实可以使用LOAD DATA INFILE;只需编写一个脚本,将您的数据转换为所需的格式,然后将其分成流程中的每个表文件。然后,您可以LOAD将每个文件放入正确的表中。该脚本可以用任何语言编写。

除此之外,批量INSERT (column, ...) VALUES ...也有效。不要猜测你的行批量大小应该是多少; 根据经验,因为最佳批量大小取决于您的特定数据库设置(服务器配置,列类型,索引等)

批量INSERT不会像LOAD DATA INFILE一样快,您仍然需要编写脚本来将原始数据转换为可用的INSERT查询。出于这个原因,如果可能的话,我可能会LOAD DATA INFILE

答案 3 :(得分:4)

FWIW以下步骤导致LOAD DATA INFILE

的巨大加速
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET SESSION tx_isolation='READ-UNCOMMITTED';
SET sql_log_bin = 0;
#LOAD DATA LOCAL INFILE....
SET UNIQUE_CHECKS = 1;
SET FOREIGN_KEY_CHECKS = 1;
SET SESSION tx_isolation='READ-REPEATABLE';

参见文章here

答案 4 :(得分:2)

我不喜欢其他一些答案:)

我曾经在工作中这样做。

您编写了一个程序来创建一个充满INSERT语句的大型SQL脚本,每行一个。比你运行脚本。您可以保存脚本以供将来参考(廉价日志)。使用gzip,它会缩小90%的大小。

您不需要任何花哨的工具,您使用的数据库并不重要。

您可以在一次交易中为每笔交易或所有交易执行几百次插入,这取决于您。

Python是一个很好的语言,但我确信php也很好。

如果遇到性能问题某些数据库(如Oracle)有一个特殊的批量加载程序,它比INSERT语句更快。

您应该耗尽内存,因为您一次只能解析一行。你没有必要把整件事记在内存中,不要那样做!

答案 5 :(得分:2)

您可以使用Mysql LOAD DATA INFILE statemnt,它允许您从文本文件中读取数据并将文件的数据快速导入数据库表中。

LOAD DATA INFILE '/opt/lampp/htdocs/sample.csv' INTO TABLE discounts FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 ROWS (title,@expired_date,discount) SET expired_date = STR_TO_DATE(@expired_date, '%m/%d/%Y');

了解更多信息:http://dev.mysql.com/doc/refman/5.5/en/load-data.htmlhttp://www.mysqltutorial.org/import-csv-file-mysql-table/

答案 6 :(得分:1)

如果您使用的是Sql Server并且可以访问.NET,那么您可以编写一个快速应用程序来使用SQLBulkCopy类。我在以前的项目中使用它来非常快速地将大量数据导入SQL。 SQLBulkCopy类使用SQL Server的BCP,因此如果您使用的是.NET以外的其他东西,那么该选项是否对您开放也许值得考虑。不确定您是否使用SQL Server以外的数据库。

答案 7 :(得分:1)

您可以使用生成器来准备内存高效的文件。下面的小片段可能会对你有所帮助。

#Method
public function getFileRecords($params)
{
    $fp = fopen('../' . $params['file'] . '.csv', 'r');
    //$header = fgetcsv($fp, 1000, ','); // skip header

    while (($line = fgetcsv($fp, 1000, ',')) != FALSE) {
        $line = array_map(function($str) {
            return str_replace('\N', '', $str);
        }, $line);

        yield $line;
    }

    fclose($fp);

    return;
}

#Implementation
foreach ($yourModel->getFileRecords($params) as $row) {
    // you get row as an assoc array;
    $yourModel->save($row);
}

答案 8 :(得分:0)

我正在读取一个包含近1M条记录和65列的CSV文件。在PHP中处理的每1000条记录中都有一条进入数据库的大胖MySQL语句。写作不花时间。这是解析。用于处理此未压缩的600MB文件的内存大约为12 MB。

答案 9 :(得分:0)

我还需要不时地执行此操作(导入大型非标准化CSV,其中每行创建十几个相关的DB对象),所以我编写了一个python脚本,我可以在其中指定内容和方式#39 ;所有相关的。然后该脚本只生成INSERT语句。

这是:csv2db

免责声明:就数据库而言,我基本上是一个菜鸟,因此可能有更好的方法来实现这一目标。