【发布时间】:2014-01-02 11:43:34
【问题描述】:
我被这个难住了。我对 PHP/MySQL 的初级水平略高于初级水平,并且在这个网站上发帖时非常新。 GoDaddy 将我切换到网格服务器以提高性能并阐明我编写脚本的方式存在的问题。它从 CSV 中抓取数据并尝试插入到规范化数据库中。
CSV 未标准化,因此需要进行大量检查以查看是否存在某些内容。我最初有它打开/关闭结果集,但后来有人建议我使用准备好的语句,不幸的是我遇到了同样的问题。在得到广泛的“内部服务器错误”之前,我可以通过大约 1200 条 14k 记录。日志中的错误引用了一项安全功能,该功能可防止在短时间内过多地访问 FastCGI 服务器。
我想要找出和学习的是完成我想要做的事情的正确方法——检查是否存在某些东西;如果是,则获取记录 ID。如果没有,则插入数据并获取新 ID。我的代码如下。它从一个简单的 php 文件上传表单中获取文件名和隐藏属性,然后从那里开始。这只会由我使用,并且我插入的数据是公共记录,因此安全性不是主要问题。
<?php
if ($_POST["upload"] == "1") {
//Connect to the database
$hostname = xxx;
$username = xxx;
$dbname = xxx;
$password = xxx;
$dbh = mysqli_connect($hostname,$username,$password,$dbname) or die("Problem connecting: ".mysqli_error());
$stmt = mysqli_stmt_init($dbh);
//check for file errors
if ($_FILES["file"]["error"] > 0)
{ echo "Return Code: " . $_FILES["file"]["error"] . "<br>"; }
//No file errors
else
{
//If file already exists
if (file_exists($_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists.";
exit;
}
//If it doesn't exist
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
$_FILES["file"]["name"]);
echo "Stored in: " . $_FILES["file"]["name"] . "<br><br>";
$strFileName = $_FILES["file"]["name"];
}
}
//File reporting
echo "Upload: " . $_FILES["file"]["name"] . "<br>";
echo "Type: " . $_FILES["file"]["type"] . "<br>";
echo "Size: " . ($_FILES["file"]["size"] / 1024) . " kB<br>";
echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br>";
$row = 0;
if (($handle = fopen($strFileName, "r")) !== FALSE) {
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
$num = count($data);
$row++;
$strPermitNo = trim($data[0]);
//Check to see if the permit is already in the database
$sql = "SELECT LocID FROM tbl_TABC_Locations WHERE LocPermitNo = ?";
if (mysqli_stmt_prepare($stmt, $sql))
{
mysqli_stmt_bind_param($stmt, "s", $strPermitNo);
mysqli_stmt_bind_result($stmt, $intLocID);
mysqli_stmt_execute($stmt);
$strPermitResult = 0;
while (mysqli_stmt_fetch($stmt))
{
$strPermitResult = $intLocID;
}
}
//If no permits, insert it
if ($strPermitResult == "0")
{
//Clean Location name
$strLocName = trim($data[1]);
$strLocName = str_replace('"', "", $strLocName);
$strLocName = str_replace(";","-", $strLocName);
$strLocName = addslashes($strLocName);
$strInsertQuery = "INSERT INTO tbl_TABC_Locations (LocName,LocAddress,LocCity,LocState,LocZip,LocCounty,LocPhone,LocPermitNo) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
if (mysqli_stmt_prepare($stmt, $strInsertQuery))
{
mysqli_stmt_bind_param($stmt, 'ssssiiis', $field1, $field2, $field3, $field4, $field5, $field6, $field7, $field8);
$field1 = $strLocName;
$field2 = trim(addslashes($data[2]));
$field3 = trim(addslashes($data[3]));
$field4 = trim($data[4]);
$field5 = trim($data[5]);
$field6 = trim($data[6]);
$field7 = trim($data[7]);
$field8 = $strPermitNo;
mysqli_stmt_execute($stmt);
$intLocID = mysqli_insert_id($dbh);
}
}
else
{
$intLocID = $strPermitResult;
}
//Report dates
$strReportDate = trim($data[8]);
$aryNewDate = explode("/", $strReportDate);
$strNewYear = $aryNewDate[0];
$strNewMonth = $aryNewDate[1];
//Check to see if the report date is already in there
$sql = "SELECT ReportDateID FROM tbl_TABC_ReportDates WHERE ReportYear = ? AND ReportMonth = ?";
if (mysqli_stmt_prepare($stmt, $sql))
{
mysqli_stmt_bind_param($stmt, "ii", $strNewYear, $strNewMonth);
mysqli_stmt_bind_result($stmt, $intReportDateID);
mysqli_stmt_execute($stmt);
$strReportDateResult = 0;
while (mysqli_stmt_fetch($stmt))
{
$strReportDateResult = $intReportDateID;
}
}
if ($strReportDateResult == "0")
{
$strInsertQuery = "INSERT INTO tbl_TABC_ReportDates (ReportMonth,ReportYear) VALUES (?, ?)";
if (mysqli_stmt_prepare($stmt, $strInsertQuery))
{
mysqli_stmt_bind_param($stmt, "ii", $field1, $field2);
$field1 = $strNewMonth;
$field2 = $strNewYear;
mysqli_stmt_execute($stmt);
$intDateID = mysqli_insert_id($dbh);
}
}
else
{
$intReportDateID = $strReportDateResult;
}
//Check to see if they have reported for the month already, and if not, add the report
$sql = "SELECT ReportID FROM tbl_TABC_Reports WHERE ReportDateID = ? AND LocID = ?";
if (mysqli_stmt_prepare($stmt, $sql))
{
mysqli_stmt_bind_param($stmt, "ii", $intReportDateID, $intLocID);
mysqli_stmt_bind_result($stmt, $intReportID);
mysqli_stmt_execute($stmt);
$strReportIDResult = 0;
while (mysqli_stmt_fetch($stmt))
{
$strReportIDResult = $intReportID;
}
}
if ($strReportIDResult == "0")
{
$strInsertQuery = "INSERT INTO tbl_TABC_Reports (LocID,ReportDateID,TaxReceipts) VALUES (?, ?, ?)";
if (mysqli_stmt_prepare($stmt, $strInsertQuery))
{
mysqli_stmt_bind_param($stmt, "iid", $field1, $field2, $field3);
$field1 = $intLocID;
$field2 = $intReportDateID;
$field3 = trim($data[9]);
mysqli_stmt_execute($stmt);
echo "New report<br>\n";
}
}
else { echo "<b>Already reported</b><br>"; }
}
echo "Closing file now";
fclose($handle);
}
mysqli_close($dbh);
}
日志中的错误是这样的:
[2436594] [fcgid:warn] (104)Connection reset by peer: [client xxx] mod_fcgid: 从 FastCGI 服务器读取数据时出错,referer(我的网页地址)
[fcgid:warn] (104)Connection reset by peer: [client xxx] mod_fcgid: ap_pass_brigade failed in handle_request_ipc function, referer(我的网页地址)
编辑 12/15(将准备好的语句拉出循环)。现在我仍然收到“准备好的语句中的变量数不匹配”错误:
$sql1 = "SELECT LocID FROM tbl_TABC_Locations WHERE LocPermitNo = ?";
$ps_ChkPermit = mysqli_stmt_prepare($stmt, $sql1);
if ($ps_ChkPermit)
{
mysqli_stmt_bind_param($stmt, "s", $strPermitNo);
mysqli_stmt_bind_result($stmt, $intLocID);
mysqli_stmt_execute($stmt);
...
}
【问题讨论】:
-
我有一个类似的脚本,可以毫无问题地解析成千上万条记录。你用什么运行php?阿帕奇? Nginx?检查服务器日志并查看错误
-
马上做两件事。首先:您应该在进入处理 CSV 数据的循环之前准备一次您的语句。在循环内只是绑定和执行。其次,不要检查和插入。在架构中将要检查的关键字段设置为 UNIQUE,然后只需插入即可。如果您有重复的 MySQL 将标记您可以检查的错误。如果你没有,你可以从
last_insert_id()检索id -
你得到什么错误?如果您使用的是 linux,只需键入 tail /var/www/apache2/error.log
-
“日志中的错误引用了安全功能” - 请将该详细信息添加到您的问题中。 “我可以通过大约 1200 条 14k 记录” - 听起来像是超时,而不是 DoS 保护。这需要多长时间?在线过程中需要超过几秒钟的任何内容都应优化或移至离线系统。
-
好的,超时了。您的脚本需要很长时间才能完成。同时,如果您急需完成,您可以将 CSV 拆分为一口大小……