在 Perl 中使用 CSV 更新 MYSQL 数据库?

Posted

技术标签:

【中文标题】在 Perl 中使用 CSV 更新 MYSQL 数据库?【英文标题】:Updating MYSQL database using CSV in Perl? 【发布时间】:2011-08-04 16:17:59 【问题描述】:

我正在尝试使用 perl 更新 mysql 数据库。我尝试了不同的方法,所有方法都非常慢。更新 14k 条记录需要 10-15 分钟。我认为最好的方法是创建存储过程并从 perl 文件中调用它,但响应时间仍然相同。

elsif($update eq "incrementalLoad") 
    $filename = param("customPricing");
    $handle = upload("customPricing");
    while (<$handle>)                  
        $currentRecord++;
        @currentLine = split( /,/, $_ );
        $i = 0;
        foreach $l(@currentLine)
            $currentLine[$i] =~ s/\\r//g; 
            $i++;           
               
        $query = "CALL upsertIncremental('$currentLine[0]', '$currentLine[1]', '$currentLine[2]', '$currentLine[3]', '$currentLine[4]', '$currentLine[5]', '$currentLine[6]', '$currentLine[7]', '$currentLine[8]', '$currentLine[9]', '$currentLine[10]', '$currentLine[11]', '$currentLine[12]', '$currentLine[13]', '$currentLine[14]', '$currentLine[15]', '$currentLine[16]', '$currentLine[17]', '$currentLine[18]', '$currentLine[19]', '$currentLine[20]', '$currentLine[21]', '$currentLine[22]', '$currentLine[23]', '$currentLine[24]', '$currentLine[25]')";
        $sth = $dbh->do($query) or die "Afasdf";
       
    print $currentRecord . " Record(s) uploaded.<br/>";
    $dbh->disconnect;           

我可以做些什么来提高性能?

这样更好吗?

elsif($update eq "incrementalLoad") 
    $filename = param("customPricing");
    $handle = upload("customPricing");

    my $update_handle = $dbh->prepare_cached("UPDATE custompricingtest SET partNumberSKU= ?, customerClass= ?, customerName= ?, customerId= ?, customerNumber= ?, custPartNumber=?, svcType= ?, sppl= ? , svcDuration= ?, durationPeriod= ?, priceMSRP= ?, partnerPriceDistiDvarOEM= ?, msrpSvcPrice=?, partnerSvcPrice=?, msrpBundlePrice=?, partnerBundlePrice=?, startDate=?, endDate=?, currency=?, countryCode=?, inventoryItemId=?, flexField1=?, flexField2=?, flexField3=?, flexField4=?, flexField5=? WHERE partNumberSKU=? and ifnull(customerClass,0)=ifnull(?,0) and ifnull(customerName,0)=ifnull(?,0) and ifnull(svcType,0)=ifnull(?,0) and ifnull(svcDuration,0)=ifnull(?,0) and ifnull(durationPeriod,0)=ifnull(?,0)") or $error = 1;

    while (<$handle>)                  
        $currentRecord++;
        @currentLine = split( /,/, $_ );
        $i = 0;
        foreach $l(@currentLine)
            $currentLine[$i] =~ s/\\r//g; 
            $i++;           
               
        $update_handle->execute($currentLine[0],$currentLine[1],$currentLine[2],$currentLine[3],$currentLine[4],$currentLine[5],$currentLine[6],$currentLine[7],$currentLine[8],$currentLine[9],$currentLine[10],$currentLine[11],$currentLine[12],$currentLine[13],$currentLine[14],$currentLine[15],$currentLine[16],$currentLine[17],$currentLine[18],$currentLine[19],$currentLine[20],$currentLine[21],$currentLine[22],$currentLine[23],$currentLine[24],$currentLine[25],$currentLine[0],$currentLine[1],$currentLine[2],$currentLine[6],$currentLine[8],$currentLine[9]) or die "can't execute UPDATE  query. \n";
        print $currentRecord . "<br/>";
       
    print $currentRecord . " Record(s) uploaded.<br/>";
    $dbh->disconnect;           

表格格式

  CREATE TABLE `custompricingtest` (
  `partNumberSKU` varchar(255) DEFAULT NULL,
  `customerClass` varchar(255) DEFAULT NULL,
  `customerName` varchar(255) DEFAULT NULL,
  `customerId` varchar(255) DEFAULT NULL,
  `customerNumber` varchar(255) DEFAULT NULL,
  `custPartNumber` varchar(255) DEFAULT NULL,
  `svcType` varchar(255) DEFAULT NULL,
  `sppl` varchar(255) DEFAULT NULL,
  `svcDuration` varchar(255) DEFAULT NULL,
  `durationPeriod` varchar(255) DEFAULT NULL,
  `priceMSRP` varchar(255) DEFAULT NULL,
  `partnerPriceDistiDvarOEM` varchar(255) DEFAULT NULL,
  `msrpSvcPrice` varchar(255) DEFAULT NULL,
  `partnerSvcPrice` varchar(255) DEFAULT NULL,
  `msrpBundlePrice` varchar(255) DEFAULT NULL,
  `partnerBundlePrice` varchar(255) DEFAULT NULL,
  `startDate` varchar(255) DEFAULT NULL,
  `endDate` varchar(255) DEFAULT NULL,
  `currency` varchar(255) DEFAULT NULL,
  `countryCode` varchar(255) DEFAULT NULL,
  `inventoryItemId` varchar(255) DEFAULT NULL,
  `flexField1` varchar(255) DEFAULT NULL,
  `flexField2` varchar(255) DEFAULT NULL,
  `flexField3` varchar(255) DEFAULT NULL,
  `flexField4` varchar(255) DEFAULT NULL,
  `flexField5` varchar(255) DEFAULT NULL,
  KEY `part_num_sku` (`partNumberSKU`),
  KEY `svcType` (`svcType`),
  KEY `svcDuration` (`svcDuration`),
  KEY `durationPeriod` (`durationPeriod`),
  KEY `customerClass` (`customerClass`)
) 

我最终将文件保存在临时表中(需要几秒钟),然后对表进行更新连接,但速度仍然很慢。下面是更新查询

UPDATE custompricingtest t1, custompricingtesttemp t2 
SET t1.customerId         = t2.customerId, 
    t1.customerNumber     = t2.customerNumber, 
    t1.custPartNumber     = t2.custPartNumber, 
    t1.sppl               = t2.sppl , 
    t1.priceMSRP          = t2.priceMSRP, 
    t1.partnerPriceDistiDvarOEM = t2.partnerPriceDistiDvarOEM,
    t1.msrpSvcPrice       = t2.msrpSvcPrice, 
    t1.partnerSvcPrice    = t2.partnerSvcPrice, 
    t1.msrpBundlePrice    = t2.msrpBundlePrice,
    t1.partnerBundlePrice = t2.partnerBundlePrice, 
    t1.startDate          = t2.startDate, 
    t1.endDate            = t2.endDate, 
    t1.currency           = t2.currency, 
    t1.countryCode        = t2.countryCode, 
    t1.inventoryItemId    = t2.inventoryItemId, 
    t1.flexField1         = t2.flexField1, 
    t1.flexField2         = t2.flexField2, 
    t1.flexField3         = t2.flexField3, 
    t1.flexField4         = t2.flexField4, 
    t1.flexField5         = t2.flexField5
WHERE t1.partNumberSKU  = t2.partNumberSKU 
  and t1.customerClass  = t2.customerClass 
  and t1.customerName   = t2.customerName 
  and t1.svcType        = t2.svcType 
  and t1.svcDuration    = t2.svcDuration 
  and t1.durationPeriod = t2.durationPeriod

解释扩展结果

id  select_type     table   type    possible_keys                                                               key               key_len   ref                            rows     Extra
1   SIMPLE           t2     ALL     part_num_sku,customerName,customerClass,durationPeriod,svcDuration,svcType  NULL                NULL    NULL                          28758      
1   SIMPLE           t1     ref     part_num_sku,svcDuration,customerName,customerClass,durationPeriod,svcType  part_num_sku         13     testbrocade.t2.partNumberSKU    394     Using where

尼特什

【问题讨论】:

在导入期间禁用索引,然后重新启用/更新索引。 我试过了..还是没用。我最后贴出来的代码是不是比别人好? 准备好的语句将减少 sql 解析/编译开销,但很可能瓶颈在其他地方。 这就是我在 3 天后想要弄清楚的……是因为我复杂的更新声明吗?我正在设置大约 25 列并使用 if(null) 条件检查 5 列 可能 - 由于您在 where 子句中比较派生值,因此可以排除使用索引。尝试在 mysql 监视器中手动运行其中一个查询并“解释”它,以查看正在使用哪些索引。如果无法使用那些 ifnull()'d 字段的索引,那么您将对每一行进行一堆全表扫描。 【参考方案1】:

不要使用存储过程;在同一语句中执行多行。根据您的 max_allowed_pa​​cket,您应该能够在一个或几个插入语句中完成所有行。

糟糕,抱歉,不知何故我将更新误读为插入。更新更难;如果您要展示您的创建表,我将尝试展示一个示例。

【讨论】:

感谢您的回复...您能告诉我更多关于如何在同一语句中执行多行的详细信息吗? 如何使用 LOAD DATA INFILE WITH REPLACE 功能?我不能以超快的速度实现相同的功能吗? 我最后添加了改进的代码..这是你的意思吗?还是有进一步优化的空间? 对于 14k 行,我不知道我会为 LOAD DATA INFILE 烦恼。您可以将show create table customerpricingtest 的输出添加到您的问题中吗? 我刚刚做了...后来我意识到我不能使用 LOAD DATA INFILE 因为更新条件基于非唯一列。 14k 一直在更新,可能是因为我的更新查询复杂【参考方案2】:

开始一个事务,做所有的插入,最后提交。这让 mySql 可以为每个插入管理一个事务。

【讨论】:

您尝试过这种方法吗?即使在赏金 anwser 中,我也建议在单个事务中对临时表进行所有插入,以提高性能【参考方案3】:

我以前做过这样的脚本。你可以用书挡来做。

    初始化步骤是创建一个临时表——保证不存在争用,并且不需要索引。 每条记录的操作是批量插入或 proc, 然后最后一步/proc 是将临时表中的更改提交到事务中的主表(并处置临时表)。

对于最后一步,您甚至可能需要删除并创建索引以加快速度。

【讨论】:

感谢您的回复...您介意分享您的脚本吗?我在这方面有点新手.. 我像你说的那样添加了两个表的更新连接......它仍然需要永远......我在描述区域添加了上面的代码......我做错了什么吗? 您可能想通过设置行数来“分块”它。更新和删除,更新和删除。在具有大量数据的大型系统上曾经有过一两次,我们无法运行更新脚本,除非在星期六或其他时间。有时运行时只是运行时。【参考方案4】:

我不知道您的数据是什么样的,但如果您有一个纯数字的索引列(partNumberSKU?),您可能会考虑将列类型更改为 int。这应该使关键比较更快。如果您使用的是临时表方法,则需要在每个表中进行相同的更改(并确保两者都已编入索引)。

如果您正在研究要索引哪些列,请针对那些具有大量唯一值的列。

【讨论】:

如果您想尝试更引人注目的解决方案,您可以将所有 varchar(255) 列转换为 char(255) (或实际需要的任何大小)。您将使用更多的存储空间,并且可能由于数据库大小的增加而失去一些性能,但我的理解是,拥有固定长度的行可以显着加快更新速度。这可能不是一个理想的解决方案,只有在您使用 MyISAM 表时才会有所帮助。有关详细信息,请参阅this post。【参考方案5】:

我认为主要问题是 MySQL 只能在每个表中使用 1 个索引,每个查询。您已分别索引您的列:

KEY `part_num_sku` (`partNumberSKU`),
KEY `svcType` (`svcType`),
KEY `svcDuration` (`svcDuration`),
KEY `durationPeriod` (`durationPeriod`),
KEY `customerClass` (`customerClass`)

但它只能使用其中一个键(优化器会尝试找出哪个键)。也许您需要一个复合键,由您需要的列组成?索引会很大,但它可能会有所帮助。或者它可能太大而没有。

记住:在 MySQL 复合索引中,索引中的列必须以从左到右的顺序在查询中使用,否则将不会被使用。这可能会有所帮助:http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html

【讨论】:

以上是关于在 Perl 中使用 CSV 更新 MYSQL 数据库?的主要内容,如果未能解决你的问题,请参考以下文章

使用 CSV 文件更新 MySQL 表

在 sql 结果中填充空日期的最直接方法是啥(在 mysql 或 perl 端)?

使用 awk 或 perl 从 CSV 中提取特定列(解析)

如何使用正则表达式解析 Perl 中引用的 CSV?

如何使用 CSV(平面文件)更新 MySQL 数据库并在插入前验证数据

使用php从csv快速更新大量数据mysql [关闭]