加速此代码 - 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 - 短代码,但目前需要非常长的时间的主要内容,如果未能解决你的问题,请参考以下文章