需要 PHP 逐行处理的 CSV 需要很长时间(或超时)才能进入 SQL 数据库

Posted

技术标签:

【中文标题】需要 PHP 逐行处理的 CSV 需要很长时间(或超时)才能进入 SQL 数据库【英文标题】:CSV that needs to be processed row by row by PHP takes ages (or timeouts) to get into a SQL database 【发布时间】:2018-01-10 02:02:46 【问题描述】:

我有一个比较网站,我每天晚上都会在该网站上更新来自商店的提要。这些通常不是最大的提要(即最多 15.000 行),但即使是只有 1000 行的 CSV 提要也可能需要很长时间,因为我必须执行(很多?)SQL 查询。这是我的程序:

每个 CSV 提要都来自不同的联属网络,因此具有不同的索引和结构。这就是为什么我编写了一个页面,首先将每个提要标准化为正确的结构(即,一个提要中的 $row['name'] = $col[1],而另一个提要中的 $col[2])。这些“正确”的数据将由另一个函数 CheckProduct(); 处理。 在 CheckProduct() 中,我首先检查产品是否已经存在。如果是,那么我正在更新标准信息:当前价格、库存、网址等。如果不是,我正在将产品插入数据库。

但是最后一点需要很长时间,即使是一个额外的查询也可能需要几分钟。我不认为它需要这么长时间,但我一直在寻找这段代码并寻找 SQL 查询优化,但我似乎无法弄清楚如何保持这个过程,当我是批量或组合 CSV行。我已经尝试过优化查询(使用 COUNT() 而不是 num_rows,只选择我需要的列等)。

我不是编程专家,但我知道肯定有不同的选择。我知道你可以将东西存储在一个数组中然后循环它,或者使用 LOAD DATA INFILE,但我不知道在这种情况下如何编程(并且仍然对每一行进行同样的严格检查)。换句话说:如何优化这段代码?

这是函数 CheckProduct()

    function CheckProduct($SiteID, $StoreID, $FeedID, $Name, $URL, $Description, $EANSKU, $Image, $Brand, $Color, $Price, $CategoryPath, $Stock, $Deliverycosts, $Deliverytime, $Length, $Width, $Depth, $Height, $Material)

    global $db;

    if ( !filter_var($URL, FILTER_VALIDATE_URL) === false && !filter_var($Image, FILTER_VALIDATE_URL) === false && !empty($Name) && !empty($URL) && !empty($Price) && !empty($Image) ) 

        $sCountProduct = $db->query("SELECT COUNT(*) as total FROM furniture WHERE name_slug='".CreateSlug(trim($db->real_escape_string($Naam)))."' AND feed_id='".$FeedID."' LIMIT 1");

            $fCountProduct = $sCountProduct->fetch_assoc();
            if($fCountProduct['total'] == '0') 

                $iProduct = $db->query("INSERT INTO furniture (site_id,feed_id,store_id,name,name_slug,affiliate_url,description,ean_sku,image_big,brand,brand_slug,color,color_slug,price,category_path,in_stock,visible,shipping_costs,check_today,last_update,deliverytime,length,width,depth,height,material,material_slug,added) VALUES ('1','".$FeedID."','".$StoreID."','".trim($db->real_escape_string($Name))."','".CreateSlug(trim($db->real_escape_string($Name)))."','".trim($AffiliateURL)."','".$db->real_escape_string(trim($Description))."', '".$db->real_escape_string(trim($EANSKU))."','".trim($Afbeelding)."','".$db->real_escape_string(trim($Brand))."','".CreateSlug($db->real_escape_string(trim($Brand)))."','".$db->real_escape_string(trim($Color))."','".CreateSlug($db->real_escape_string(trim($Color)))."','".$db->real_escape_string(trim($Price))."','".$db->real_escape_string(trim(strtolower($CategoryPath)))."','".$Stock."', '3','".$db->real_escape_string(trim($Deliverycosts))."','1','".time()."','".$db->real_escape_string(trim($Deliverytime))."','".$db->real_escape_string(trim($Length))."','".$db->real_escape_string(trim($Width))."','".$db->real_escape_string(trim($Depth))."','".$db->real_escape_string(trim($Height))."','".$db->real_escape_string(trim($Material))."','".CreateSlug($db->real_escape_string(trim($Material)))."','".date('d-m-Y')."')");

                $IDProduct = $db->insert_id;

                if($iProduct) 
                    $dOthers = $vm->query("DELETE FROM furniture WHERE name_slug='".CreateSlug(trim($db->real_escape_string($Name)))."' AND id != '".$IDProduct."' AND feed_id='".$FeedID."' AND visible != '1'");
                
            
            else 
                $sExistProduct = $db->query("SELECT id,site_id,name_slug,feed_id,price_old,visible,price FROM furniture WHERE site_id='1' AND name_slug='".CreateSlug(trim($db->real_escape_string($Name)))."' AND feed_id='".$FeedID."' LIMIT 1");

                if(!$sExistProduct)  
                else 
                    // Check if it is a salesproduct
                $fExistProduct = $sExistProduct->fetch_assoc();
                $OudePrijs = $fExistProduct['price_old'];
                $Zichtbaar = $fExistProduct['visible'];

                if($fExistProduct['visible'] == '2') $Visible = '1';              
                if($Price < $fExistProduct['price']) $OldPrice = $fExistProduct['price']; 

                $uProduct = $db->query("UPDATE furniture SET affiliate_url='".trim($URL)."', description='".$db->real_escape_string(trim($Description))."', price='".$db->real_escape_string(trim($Price))."', price_old='".$db->real_escape_string(trim($OldPrice))."', in_stock='".$Stock."', shipping_costs='".$db->real_escape_string(trim($Deliverycosts))."', check_today='1', last_update='".time()."', deliverytime='".$Deliverytime."', visible='".$Visible."' WHERE id='".$fExistProduct['id']."' LIMIT 1");

                    if($uProduct) 
                    // Updated
                    
                
            
    
    else
    
        // Error in image (not every image send by the feed is a good one)
     

这是我处理其中一个提要以匹配正确列的方式:

    $SiteID = '1';
$FeedID = '1';
$StoreID = '1';

$Link = 'URL';

if (($handle = fopen($Link, "r")) !== FALSE) 
    fgetcsv($handle);
    $i = 0;

    while (($data = fgetcsv($handle, 6000, ";")) !== FALSE) 
        $num = count($data);
        for ($c=0; $c < $num; $c++) $col[$c] = $data[$c]; 

        ### VARIABLES FROM FEED ###
        $Name = $col[1]; 
        $URL = $col[5]; 
        $Description = strip_tags($col[4]); 
        $EANSKU = $col[0]; 
        $Image = $col[6]; 
        $Brand = $col[8]; 
        $Color = ''; 
        $Price = $col[3]; 
        $CategoryPath = $col[9]; 
        $Stock = $col[10]; 
        $Deliverycosts = $col[14]; 
        $Deliverytime = '';

        $Length = '';
        $Width = '';
        $Height = '';
        $Depth = ''; 
        $Material = ''; 

        ### STOCK ###
        if($Stock > 0) 
            $Stock = '1';
        
        else 
            $Stock = '0';
        

        ### SEND PRODUCT ###
        CheckProduct($SiteID, $StoreID, $FeedID, $Name, $URL, $Description, $EANSKU, $Image, $Brand, $Color, $Price, $CategoryPath, $Stock, $Deliverycosts, $Deliverytime, $Length, $Width, $Depth, $Height, $Material);
        $i++;

    

    fclose($handle);

我正在使用 php 5.6.32 和 mysql 5.0.11。

【问题讨论】:

【参考方案1】:

SELECT COUNT(*) 不需要限制...

只需预先选择数据: SELECT name_slug,feed_id, COUNT(*) as cnt as total FROM Furniture GROUP BY name_slug,feed_id");

SELECT name_slug,feed_id, id,site_id,name_slug,feed_id,price_old,visible,price FROM furniture WHERE site_id='1'

并将它们用作查找表。

然后批量插入/删除/更新,整个脚本应该 不到几秒。

或者全部在mysql中完成...

【讨论】:

谢谢。我想我会选择你的最新选择。我现在要做的是:1)使用标准化数据创建一个新的 CSV 文件 2)尝试使用 ON DUPLICATE KEY 加载数据文件。 (来自本主题:***.com/questions/15271202/…)。我认为这将完成这项工作。 :) 如果您的 CSV 行需要唯一,并且所有字段都相同,只需:“cat file.csv | sort | uniq”和“LOAD DATA INFILE”,没有“ON DUPLICATE KEY”会更快

以上是关于需要 PHP 逐行处理的 CSV 需要很长时间(或超时)才能进入 SQL 数据库的主要内容,如果未能解决你的问题,请参考以下文章

如果不逐行遍历数据框,这需要很长时间,我如何检查多行是不是都满足条件?

从 csv 文件中读取数据需要很长时间 [重复]

Ruby 在处理大文件时搜索和合并 CSV 文件

回显大量 HTML 数据需要很长时间 PHP

PHP页面需要很长时间才能加载

想要从选择查询中处理 5000 条记录在 oracle 数据库中需要很长时间