我正在尝试解决数据库保存中的间歇性问题。基本上,代码使用发布的数据进行早期查询。然后,我检查该查询中的行数,以确定是更新还是插入记录。
大约1%的时间,这不起作用,并且会覆盖不相关的记录。我想知道,我使用mysql_num_rows()的比较运算符是否有问题。
使用
是否有任何可能的奇怪影响if(mysql_num_rows($Result) != 0)
后来添加:
mysql版本5.0.51a
我会尽力解释这里重要的事情。 这些表格涉及内部信贷申请。销售人员填写申请信用证的公司表格。然后,他们可以保存该申请以便稍后完成,或者发送到会计部门进行审批。会计可以保存记录以供日后使用,将其返回给销售人员或批准。在任何这些操作中,整个表单都插入(首次创建时)或在表记录中更新。
当将对表单的访问权限转移给销售人员或会计师的任何操作时,会向相应的一方发送电子邮件,其中包括指向该记录的链接。销售人员只能访问他们创建的记录。这是通过简单地检查他们在会话变量中保存的登录用户名来完成的,表中的字段也包含他们的用户名。
表单顶部是一个选择框,用于保存等待处理的记录。在该框中可供销售人员使用的是他/她已存储的记录或发送给他/她的记录以进行更正和重新提交。他们可以通过简单地选择一个来提取表格。他们还可以通过单击电子邮件链接来检索表单,在他们提交表单时发送给他们,或者管理员(会计师)将表单返回给他们。同样,会计师也可以通过两种方法对发送给他们的记录进行处理。
此过程中的每个事务都记录在“事务详细信息表”中。
有许多错误检查可防止记录被不适当地访问。 (请耐心等待,这一切都很重要)销售人员和会计师无法同时访问记录,并且一旦申请获得批准,除了查看之外都没有访问权限。
问题
所有内容都依赖于ID字段,这是CreditApp表中的自动增量mysql字段。该号码存储在“AppID”字段的日志文件中。在这些交易的大约1%中,无论是当销售人员将表单提交给管理员(会计师)还是会计师批准,而不是更新正确的记录,都会更新完全不相关的记录。每个覆盖的记录都是先前已处理的记录(意思是“由会计师”批准)。通常,但不一定,被覆盖的记录可能是一年或两年。
虽然我不确定记录是否在销售人员提交或管理员批准时被覆盖,但另一个特殊的事情是,当这种覆盖发生时,日志表中的条目由销售人员提交,没有将它们与表单记录相关联的AppID。这是空白的。
所以这里是一个非常简化的过程模拟(我相信有更多雄辩的方法可以做到这一点,但唉......):
if($Process == "RegularSave") // Salesperson storing record for later
{
$Status = "Store";
$StoreTitle = $NewTitle;
if(empty($StoreTitle)){$Error[] = "Title cannot be blank. Record was not saved!";}
$Q = "SELECT ID, StoreTitle FROM CreditApp WHERE ID = '$ID' OR UniqueID = '$UniqueID'"; // UniqueID prevents double entry on refresh of new record
$Result = mysql_query($Q);
if(!$Result){$Error[] = "Database error in storage result!";}
}
elseif($Process == "RegularSubmit") //Sslesperson submitting record
{
$Status = "Received";
$StoreTitle = $NewTitle;
if(empty($StoreTitle)){$Error[] = "Title cannot be blank. Record was not saved!";}
$Q = "SELECT ID FROM CreditApp WHERE ID = '$ID' OR UniqueID = '$UniqueID'"; // UniqueID prevents double entry on refresh of new record
$Result = mysql_query($Q);
if(!$Result){$Error[] = "Database error in ID Check!";}
}
elseif($Process == "AdminProcess" || $Process == "AdminSave" || $Process == "AdminReturn")
{
// Status variable set here as to "Revised", "Rejected", "Approved", etc.
// THEN:
$Q = "SELECT ID FROM CreditApp WHERE ID = '$ID'";
$Result = mysql_query($Q);
if(!$Result){$Error[] = "Database error in ID Check!";}
}
elseif($Process == "AdminSend")
{
// Setup for e-mail from admin when returning record for corrections
$ReturnDate = dFormat($Time,41);
$FromName = $AdminName;
$FromEmail = $AdminAddress;
$ReturnUser = $_SESSION['FullName'];
$DetailMsg = nl2br($Message);
$NoteString = '======================='."\n".$ReturnUser.': '.$Today."\n".$Message."\n".'======================='."\n".$Notes;
$R = mysql_query("UPDATE CreditApp SET Notes = '$NoteString', Status = 'Return', ReturnDate = '$ReturnDate', ReturnUser = '$ReturnUser', AdminID = '$_SESSION[User]' WHERE ID = '$ID'");
$M = mysql_query("INSERT INTO CustAcctStatsDetail (AppID,Action,Detail,Form,TranUser) VALUES ('$ID','Return for Corrections','$DetailMsg','$FormName','$_SESSION[User]')");
$HTMLData = ('Your credit request for '.$AcctName.' has been returned for the following reasons:<br /><br />'.nl2br($Message).'<br /><br />
FormLink: You can access the record from <a href="'.$MainDir.'credit.php?iKey='.$ID.'&ret=1">this link</a>.<br />
You will also find it available in your stored records list at the top of the Credit Application Request form.
<br /><br />');
}
if(count($Error) == 0 && $Process != "AdminSend")
{
if(mysql_num_rows($Result) != 0) // Indicates record already exists
{
#=====================================================#
# Update Existing Record #
#=====================================================#
$X = mysql_fetch_array($Result);
$Q = "UPDATE CreditApp ... WHERE ID = '$X[ID]'"; // Standard Update set of fields
$Result = mysql_query($Q);
if(!$Result){$Error[] = "Database update error! (1) ApproveDate: ".$ApproveDate.' '.mysql_error();}
else
{
// Here related tables are updated (simple one-to-many relationships for form data)
// THEN:
#=====================================================#
# Log any changes #
#=====================================================#
$LQ = "UPDATE CustAcctStats SET StoreTitle = '$StoreTitle', Company = '$AcctName',";
if($AppType == "New"){$ApprovalString = $Approval;}
elseif($Approval == "Approved"){$ApprovalString = "Completed";} // Revised entry
else{$ApprovalString = $Approval;}
if($_SESSION[GVars][Approval] != $Approval || $AppType == "Revised")
{
$StatusString = $Status.'/Credit';
$LQ .= " Status = '$StatusString', CreditApproval = '$ApprovalString', CreditDate = '$ThisDate',";
}
$TAction=array(); $TDetail=array();
if($_SESSION[GVars][SubmitDate] != $SubmitDate)
{
$TAction[] = 'Form Submitted';
$TDetail[] = $AppType != "Revision" ? "Credit Application submitted for approval" : "Credit Revision Request submitted";
$LQ .= " SubmitDate = '$SubmitDate'";
}
if($_SESSION[GVars][Approval] != $Approval || $_SESSION[GVars][SubmitDate] != $SubmitDate || $AppType == "Revised")
{
if(substr($LQ,-1) == ','){$LQ = substr($LQ,0,-1);}
$LQ .= " WHERE AppID = '$ID'";
$Result = mysql_query($LQ);
if(!$Result){$Error[] = "Log File Error! [1] ".mysql_error();}
}
if($_SESSION[GVars][Approval] != $Approval || $AppType == "Revised")
{
if($AppType != "Revised")
{
if($Approval == "Approved")
{
$TAction[] = '<span class="LogBlue">Credit Application Approved</span>'; $TDetail[] = 'This Credit Application has been approved for '.$CreditAmt;
}
elseif($Approval == "Declined")
{
$TAction[] = '<span class="LogRed">Credit Application Declined</span>'; $TDetail[] = 'This Credit Application has been declined';
}
}
else
{
if($Approval == "Approved")
{
$TAction[] = '<span class="LogBlue">Credit Revision Approved</span>'; $TDetail[] = 'This submitted credit revision has been approved and completed.';
}
elseif($Approval == "Rejected")
{
$TAction[] = '<span class="LogRed">Credit Revision Rejected</span>'; $TDetail[] = 'This Credit Revision has been rejected';
}
}
}
if($_SESSION[GVars][Status] != $Status)
{
$TAction[] = 'Status Change';
if(!empty($_SESSION[GVars][Status]))
{
$TDetail[] = 'Status change from '.$_SESSION[GVars][Status].' to '.$Status;
}
else
{
$TDetail[] = 'Status change set to '.$Status;
}
}
if($_SESSION[GVars][StoreTitle] != $StoreTitle)
{
$TAction[] = 'Store Title Change';
if(empty($_SESSION[GVars][StoreTitle]))
{
$TDetail[] = 'Store Title created: '.$StoreTitle;
}
else
{
$TDetail[] = 'Store Title change from '.$_SESSION[GVars][StoreTitle].' to '.$StoreTitle;
}
}
$TranCount = count($TAction);
for($a=0;$a<$TranCount;$a++)
{
$Q = "INSERT INTO CustAcctStatsDetail (AppID,Action,Detail,Form,TranUser) VALUES ('$ID','$TAction[$a]','$TDetail[$a]','$FormName','$_SESSION[FullName]')";
$Result = mysql_query($Q);
if(!$Result){$Error[] = "Log File Error! [2]";}
if($Status == "Processed")
{
$Q = "UPDATE CustAcctStats SET StoreTitle = '$StoreTitle', Company = '$AcctName', CreditDate = CURDATE(), Date = NOW(), AdminUser = '$_SESSION[User]' WHERE AppID = '$ID'";
}
else
{
$Q = "UPDATE CustAcctStats SET StoreTitle = '$StoreTitle', Company = '$AcctName', Date = NOW() WHERE AppID = '$ID'";
}
$Result = mysql_query($Q);
}
switch($Process)
{
// Text is set here to display result and status to the user
}
}
}
elseif(!$_SESSION['Admin']) // Record is new entry. Admin only deals with records in process
{
#=====================================================#
# Create New Record #
#=====================================================#
$Q = "INSERT INTO CreditApp ..."; // Standard Insert set of fields
$Result = mysql_query($Q);
if(!$Result){$Error[] = "Error in database insert! (1) ".mysql_error($Conn);}
else
{
// Here related tables are updated (simple one-to-many relationships for form data)
// THEN:
#=====================================================#
# Create new Log Entry #
#=====================================================#
$CreditApproval = !empty($Approval) ? $Approval : "";
if(!empty($ApproveDate)){$CreditDate = $ApproveDate;}
if(!empty($DeclineDate)){$CreditDate = $DeclineDate;}
if($Process == "RegularSave")
{
if($AppType != "Revision")
{
$Action = "Record Created";
$Detail = "A new record was created but stored to submit at a later date.";
$StatusString = "Store/Credit";
}
else
{
$CreditApproval = "Current";
$Action = "Credit Revision";
$Detail = "A Credit Revision was created but stored to submit at a later date.";
$StatusString = "Store/Credit";
}
}
elseif($Process == "RegularSubmit")
{
if($AppType != "Revision")
{
$Action = "Record Created";
$Detail = "A new record was created and sent to Administration for approval.";
$StatusString = "Received/Credit";
}
else
{
$CreditApproval = "Current";
$Action = "Credit Revision";
$Detail = "A Credit Revision was sent to Administration.";
$StatusString = "Received/Credit";
}
}
else
{
$Action = "Error!";
if(empty($Process)) // "Detail text added 2/19/15 (Previously was blank)
{
$Detail = "Process variable is empty";
}
else
{
$Detail = $Process.' should equal RegularSave or RegularSubmit';
}
}
if(empty($CreditDate)){$CreditDate = "0000-00-00";}
if(empty($SubmitDate)){$SubmitDate = "0000-00-00";}
$Result = mysql_query("INSERT INTO CustAcctStats (AppID,AppType,User,StoreTitle,Company,Status,CreditApproval,CreditDate,SubmitDate,Date)
Values ('$ID','$AppType','$_SESSION[User]','$StoreTitle','$AcctName','$StatusString','$CreditApproval','$CreditDate','$SubmitDate',NOW())");
if(!$Result){$Error[] = "Log File Error! [3] ".mysql_error();}
else
{
$Result = mysql_query("INSERT INTO CustAcctStatsDetail (AppID,Action,Detail,Form,TranUser,TranDate) VALUES ('$ID','$Action','$Detail','$FormName','$_SESSION[FullName]',NOW())");
if(!$Result){$Error[] = "Log File Error! [4]";}
}
}
}
elseif($_SESSION['Admin'])
{
$Error[] = "Record not found!<br />Please exit Admin mode if you want to save a new record!";
}
}
if(($_POST['Submit'] == "Submit" || $Process == "AdminSend" || $Process == "AdminProcess") && count($Error) == 0)
{
// Here the e-mail is generated
}
答案 0 :(得分:0)
你正在使用InnoDB,对吗?你有多个连接可能做这样的查询?但你没有BEGIN ... COMMIT围绕对(SELECT,INSERT / UPDATE)语句?
切换到INSERT ... ON DUPLICATE KEY UPDATE ...
以便在单个原子操作中执行此过程。
如果你确实有BEGIN ... COMMIT,那么SELECT有FOR UPDATE
吗?它应该 - 为了锁定需要UPDATE的记录或锁定新记录将被插入的位置。
答案 1 :(得分:0)
SELECT ID, StoreTitle FROM CreditApp WHERE ID = '$ID' OR UniqueID = '$UniqueID'
这可能表现得非常缓慢。检查EXPLAIN或计时。解决方法是将其变为UNION:
( SELECT ID, StoreTitle CreditApp WHERE ID = '$ID' )
UNION DISTINCT
( SELECT ID, StoreTitle CreditApp WHERE UniqueID ='$UniqueID' )
我不知道$ R(INSERT?)或$ M(UPDATE?)的执行位置。 SELECT和INSERT / UPDATE之间需要多长时间?时间越长,另一个连接滑入的可能性就越大。
此外,如果OR很慢,你可以让多个SELECT排队,等待潜入。而没有OR的SELECT可以非常快地滑入。
根据我的理解,在INSERT / UPDATE之后的SELECT和UNLOCK TABLES之前,你真的需要LOCK TABLES WRITE ....否则,正如你所看到的,偶尔会发生一些事情。
或者,跳过LOCK / UNLOCK并将INSERT / UPDATE转换为INSERT ... ON DUPLICATE KEY UPDATE,因为它是原子的。 (即使保留了SELECT,即使它发生了蠢事,IODKU也会对其进行纠正。)