加速此代码 - PHP/SQL - 短代码,但目前需要非常长的时间

Posted

技术标签:

【中文标题】加速此代码 - PHP/SQL - 短代码,但目前需要非常长的时间【英文标题】:Speed up this code - PHP/SQL - Short Code but takes incredibly long at the moment 【发布时间】:2009-07-11 21:38:09 【问题描述】:

对,这段代码通过一个相当大的多维数组(大约有 28,000 行和 16 个部分)。

事件顺序:

    检查数据库中是否存在数据 如果存在 - 使用新数据更新它 如果它不存在 - 插入它

简单。

但现在要完成这件事,我认为它已经花费了 30 多分钟,并且 仍在进行

$counter = 0;
$events = sizeof($feed_array['1'])-1;
while($counter <= $events ) 

    $num_rows = mysql_num_rows(mysql_query("SELECT * FROM it_raw WHERE perfID = '".addslashes($feed_array['16'][$counter])."'")); 
    if($num_rows) 
        $eventDate=explode("/", $feed_array['1'][$counter]); //print_r($eventDate);
        $eventTime=explode(":", $feed_array['2'][$counter]); //print_r($eventTime);     
        $eventUnixTime=mktime($eventTime[0], $eventTime[1], "00", $eventDate[1], $eventDate[0], $eventDate[2]);

        mysql_query("UPDATE `it_raw` SET  
                    `eventtime` =       '".$eventUnixTime."',
                    `eventname` =       '".addslashes($feed_array['3'][$counter])."',
                    `venuename` =       '".addslashes($feed_array['4'][$counter])."',
                    `venueregion` =     '".addslashes($feed_array['5'][$counter])."',
                    `venuepostcode` =   '".addslashes($feed_array['6'][$counter])."',
                    `country` =         '".addslashes($feed_array['7'][$counter])."',
                    `minprice` =        '".addslashes($feed_array['8'][$counter])."',
                    `available` =       '".addslashes($feed_array['9'][$counter])."',
                    `link` =            '".addslashes($feed_array['10'][$counter])."',
                    `eventtype` =       '".addslashes($feed_array['11'][$counter])."',
                    `seaOnSaleDate` =   '".addslashes($feed_array['12'][$counter])."',
                    `perOnSaleDate` =   '".addslashes($feed_array['13'][$counter])."',
                    `soldOut` =         '".addslashes($feed_array['14'][$counter])."',
                    `eventImageURL` =   '".addslashes($feed_array['15'][$counter])."',
                    `perfID`=           '".addslashes($feed_array['16'][$counter])."'
                    WHERE  `perfID` = ".$feed_array['16'][$counter]." LIMIT 1 ;");
        echo "UPDATE ".$feed_array['16'][$counter].": ".addslashes($feed_array['3'][$counter])."\n";
     else 
        $eventDate=explode("/", $feed_array['1'][$counter]); //print_r($eventDate);
        $eventTime=explode(":", $feed_array['2'][$counter]); //print_r($eventTime);     
        $eventUnixTime=mktime($eventTime[0], $eventTime[1], "00", $eventDate[1], $eventDate[0], $eventDate[2]);
        $sql = "INSERT INTO  `dante_tickets`.`it_raw` (
                `id` ,
                `eventtime` ,
                `eventname` ,
                `venuename` ,
                `venueregion` ,
                `venuepostcode` ,
                `country` ,
                `minprice` ,
                `available` ,
                `link` ,
                `eventtype` ,
                `seaOnSaleDate` ,
                `perOnSaleDate` ,
                `soldOut` ,
                `eventImageURL` ,
                `perfID`
                )
                VALUES (
                    NULL ,  
                    '".$eventUnixTime."',  
                    '".addslashes($feed_array['3'][$counter])."',  
                    '".addslashes($feed_array['4'][$counter])."',  
                    '".addslashes($feed_array['5'][$counter])."',  
                    '".addslashes($feed_array['6'][$counter])."',  
                    '".addslashes($feed_array['7'][$counter])."',  
                    '".addslashes($feed_array['8'][$counter])."',  
                    '".addslashes($feed_array['9'][$counter])."',  
                    '".addslashes($feed_array['10'][$counter])."',  
                    '".addslashes($feed_array['11'][$counter])."',  
                    '".addslashes($feed_array['12'][$counter])."',  
                    '".addslashes($feed_array['13'][$counter])."',  
                    '".addslashes($feed_array['14'][$counter])."',  
                    '".addslashes($feed_array['15'][$counter])."',  
                    '".addslashes($feed_array['16'][$counter])."'
                );";

        mysql_query($sql) or die(mysql_error().":".$sql);
        echo "Inserted ".$feed_array['16'][$counter].": ".addslashes($feed_array['3'][$counter])."\n";
    
    unset($sql);
    $counter++;     

更新

我刚刚对其中一行进行了分析:

mysql> EXPLAIN SELECT * FROM it_raw WHERE perfID = 210968;
+----+-------------+--------+------+---------------+--------+---------+-------+------+-------+
| id | select_type | table  | type | possible_keys | key    | key_len | ref   | rows | Extra |
+----+-------------+--------+------+---------------+--------+---------+-------+------+-------+
|  1 | SIMPLE      | it_raw | ref  | perfID        | perfID | 4       | const |    1 |       | 
+----+-------------+--------+------+---------------+--------+---------+-------+------+-------+
1 row in set (0.07 sec)

更新 2

为了尝试“加快”速度,而不是立即执行 UPDATE 和 INSERT 语句,我现在将它们放在一个变量中(因此只有初始选择运行 - 检查重复项 - 然后存储操作[插入或更新])。在循环结束时,它会执行所有语句。

除了现在,它会出现语法不正确的 MySQL 错误。 (最初没有任何问题时)。

我只是简单地将 mysql_query 替换为:

$sql_exec .= "SELECT.... ;";

这里有什么我在格式化方面遗漏的东西吗?

更新 3 OK 终于修好了 经验教训:

    先对数据库进行逻辑搜索 批量执行插入/更新。

这是最终代码,现在运行大约需要 60 秒(从超过 30 分钟开始)

while($counter <= $events ) 

        $num_rows = mysql_num_rows(mysql_query("SELECT * FROM it_raw WHERE perfID = '".addslashes($feed_array['16'][$counter])."'")); 
        if($num_rows) 
            $eventDate=explode("/", $feed_array['1'][$counter]); //print_r($eventDate);
            $eventTime=explode(":", $feed_array['2'][$counter]); //print_r($eventTime);     
            $eventUnixTime=mktime($eventTime[0], $eventTime[1], "00", $eventDate[1], $eventDate[0], $eventDate[2]);

            $sql_exec[] =   "UPDATE `it_raw` SET `eventtime` = '".$eventUnixTime."',`eventname` = '".addslashes($feed_array['3'][$counter])."',`venuename` = '".addslashes($feed_array['4'][$counter])."',`venueregion` = '".addslashes($feed_array['5'][$counter])."',`venuepostcode` = '".addslashes($feed_array['6'][$counter])."',`country` = '".addslashes($feed_array['7'][$counter])."',`minprice` = '".addslashes($feed_array['8'][$counter])."',`available` = '".addslashes($feed_array['9'][$counter])."',`link` = '".addslashes($feed_array['10'][$counter])."',`eventtype` = '".addslashes($feed_array['11'][$counter])."',`seaOnSaleDate` = '".addslashes($feed_array['12'][$counter])."',`perOnSaleDate` = '".addslashes($feed_array['13'][$counter])."',`soldOut` =  '".addslashes($feed_array['14'][$counter])."',`eventImageURL` =   '".addslashes($feed_array['15'][$counter])."',`perfID`='".addslashes($feed_array['16'][$counter])."' WHERE `perfID` = ".$feed_array['16'][$counter]." LIMIT 1;";
            echo "UPDATE ".$feed_array['16'][$counter].": ".addslashes($feed_array['3'][$counter])."\n";
         else 
            $eventDate=explode("/", $feed_array['1'][$counter]); //print_r($eventDate);
            $eventTime=explode(":", $feed_array['2'][$counter]); //print_r($eventTime);     
            $eventUnixTime=mktime($eventTime[0], $eventTime[1], "00", $eventDate[1], $eventDate[0], $eventDate[2]);
            $sql_exec[] = "INSERT INTO  `it_raw` (`id` ,`eventtime` ,`eventname` ,`venuename` ,`venueregion` ,`venuepostcode` ,`country` ,`minprice` ,`available` ,`link` ,`eventtype` ,`seaOnSaleDate` ,
                    `perOnSaleDate` ,`soldOut` ,`eventImageURL` ,`perfID`) VALUES ( NULL ,'".$eventUnixTime."','".addslashes($feed_array['3'][$counter])."','".addslashes($feed_array['4'][$counter])."','".addslashes($feed_array['5'][$counter])."','".addslashes($feed_array['6'][$counter])."','".addslashes($feed_array['7'][$counter])."','".addslashes($feed_array['8'][$counter])."','".addslashes($feed_array['9'][$counter])."','".addslashes($feed_array['10'][$counter])."','".addslashes($feed_array['11'][$counter])."','".addslashes($feed_array['12'][$counter])."','".addslashes($feed_array['13'][$counter])."','".addslashes($feed_array['14'][$counter])."','".addslashes($feed_array['15'][$counter])."','".addslashes($feed_array['16'][$counter])."');";

            //mysql_query($sql) or die(mysql_error().":".$sql);
            echo "Inserted ".$feed_array['16'][$counter].": ".addslashes($feed_array['3'][$counter])."\n";
        
        unset($sql);
        $counter++;     
    

    foreach($sql_exec as $value) 
        mysql_query($value) or die (mysql_error().": ".$value); 
    

【问题讨论】:

【参考方案1】:

您可以尝试将插入和更新分组,以便代码运行更少的查询。

例如,您可以将所有插入分组为一个非常大的插入,或者每 100 个插入分组。

也使用 gradbot 建议的准备好的语句可能会有所帮助。

除此之外,很难说它的哪一部分是导致缓慢的主要原因。您应该使用分析器来确定这一点,例如使用较小的数据集,以便分析脚本在合理的时间内运行。

【讨论】:

【参考方案2】:

你可以做很多事情。

on duplicate key 而不是选择 prepared statement 如果您有权访问 PDO MySQL。 stored procedure 在 MySQL 中

试试这个。我无法测试它,但语法应该是正确的。

$counter = 0;
$events = sizeof($feed_array['1']) - 1;
while($counter <= $events )

  $eventDate = explode("/", $feed_array['1'][$counter]); //print_r($eventDate);
  $eventTime = explode(":", $feed_array['2'][$counter]); //print_r($eventTime);             
  $eventUnixTime = mktime($eventTime[0], $eventTime[1], "00", $eventDate[1], $eventDate[0], $eventDate[2]);

  $data = array(
    'eventtime'     => $eventUnixTime,
    'eventname'     => addslashes($feed_array['3'][$counter]),
    'venuename'     => addslashes($feed_array['4'][$counter]),
    'venueregion'   => addslashes($feed_array['5'][$counter]),
    'venuepostcode' => addslashes($feed_array['6'][$counter]),
    'country'       => addslashes($feed_array['7'][$counter]),
    'minprice'      => addslashes($feed_array['8'][$counter]),
    'available'     => addslashes($feed_array['9'][$counter]),
    'link'          => addslashes($feed_array['10'][$counter]),
    'eventtype'     => addslashes($feed_array['11'][$counter]),
    'seaOnSaleDate' => addslashes($feed_array['12'][$counter]),
    'perOnSaleDate' => addslashes($feed_array['13'][$counter]),
    'soldOut'       => addslashes($feed_array['14'][$counter]),
    'eventImageURL' => addslashes($feed_array['15'][$counter]),
    'perfID'        => addslashes($feed_array['16'][$counter]),
  );

  $update = array();
  foreach ($data as $key => $value)
    $update[] = "`$key` = '$value'";

  $sql = "INSERT INTO `dante_tickets`.`it_raw`" .
    '(`id`, `'. implode ('`,`', array_keys($data)) . '`) VALUES ' .
    '(NULL, ' . implode (',', $data) . ') ON DUPLICATE KEY UPDATE ' . 
    implode (',', $update);

  mysql_query($sql) or die(mysql_error().":".$sql);
  echo "Inserted or Updated".$feed_array['16'][$counter].": ".addslashes($feed_array['3'][$counter])."\n";

  unset($sql);
  $counter++;     

我忘了提到这要求 perfID 是唯一键。

【讨论】:

你有使用存储过程的例子吗?还有我不太确定的 PDO:/ 在尝试之前,我会等着看是否还有其他问题。 您为什么认为存储过程会提高其性能?你能举一个支持这一点的数据的例子吗? @MarkR 存储过程会将他的多个查询变成一个查询。仅此一项就可以加快速度,但是使用重复键可以消除这种情况。 那么 ON DUPLICATE KEY 是否更快? 酷 - 现在我的方法足够快 - 但你的代码更干净 - 所以对于下一个版本,我会尝试它。可惜堆栈溢出没有“辅助回答”功能。【参考方案3】:

    您是否分析过此查询?

    "SELECT * FROM it_raw WHERE perfID = '".addslashes($feed_array['16'][$counter])."'"

    因为你运行它 28000 次.. 所以除非它真的很快,否则它会让你头疼。如果需要,请使用EXPLAIN syntax 并添加适当的索引。

    编辑:对于配置文件,我的意思是您应该尝试在 mysql-prompt 上使用 EXPLAIN 来查看 MySQL 查询优化器为此查询建议的执行计划。即,在提示符下运行:

    EXPLAIN SELECT * FROM it_raw WHERE perfID = 426; 
    # Change this id to something existing and valid
    

    你想看到的是它正在使用一个索引,而且只有一个索引。如果您不理解输出,请将其复制并粘贴到此处,以便我与您一起浏览。

    更新:如您所见,数据数组中的每一行都需要 0.07 秒,加上实际查询数据库、传输结果等的时间。这大约是 28000 * 0.07 = 1960 秒,或 32 分钟,只是为了检查数据是否存在。你需要想出另一种方法来检查数据是否已经存在......一个非常简单的优化可能是:

    EXPLAIN SELECT perfId FROM it_raw WHERE perfID = 210968;
    

    这样就可以使用perfId上的索引,不需要访问表

    如果可能,请尽量避免为循环中的每次运行查询数据库。也许可以将数据库中的 id 提取到一个适合 php 内存的大 id 数组中?这比查询大数据数组中每一行的数据库要快得多。

【讨论】:

你能解释一下你所说的异形是什么意思吗? (前后回显微时间?) 在我的问题中添加了一个解释输出(尽管使用 phpmyadmin - 不确定它是否会影响您看到的内容[如果需要我可以使用 shell]) 您的更新没有任何问题,因为仅使用 SELECT 语句运行整个脚本大约需要 1 分钟...【参考方案4】:

这正是 prepared statements 所针对的场景:

$prepared_statement =
    $DB->prepare('INSERT INTO table(column, column) VALUES(?, ?)');
loop 
    $prepared_statement->execute(array('value1', 'value2');

它在 MySQLi 和 PDO 包装器中实现。它只编译一次查询并自动清理给定的数据,节省时间(开发和执行)和头痛。

【讨论】:

以上是关于加速此代码 - PHP/SQL - 短代码,但目前需要非常长的时间的主要内容,如果未能解决你的问题,请参考以下文章

加速此代码的提示

WordPress短代码只接受默认属性

php/sql ...我应该如何使用 50mb 文件中的数据?

我可以使用此代码依赖此“默认”更新间隔(加速度计)吗?

woocommerce产品类别短代码分页不起作用

如何使用短代码找到“页面ID”?