PHP双表插入时外键与主键冲突问题求助
解决主表主键作为从表外键的插入冲突问题
嘿,咱们来搞清楚你插入两个表时遇到外键冲突的问题。你提到的「两个查询同时执行」其实不是核心原因——mysqli_query是同步执行的,第一个查询跑完才会执行第二个。咱们一步步拆解真正的问题,然后给出解决方案:
1. 先找到外键冲突的根源
你看到的错误,大概率是第二个插入的外键值在主表里根本不存在,常见场景有这几种:
- 第一个插入
payement的语句其实执行失败了(比如主键重复、字段格式不对),但你用&&判断两个查询都成功才走成功分支,所以直接跳到了错误提示; - 如果
payement表的ID_PAYEMENT是自增主键,那你手动传入$InputID_PAYEMENT的做法本身就错了,应该让数据库自动生成主键,再拿这个生成的值插入从表; - 你的代码有严重的SQL注入风险,用户输入的特殊字符可能直接破坏SQL语句结构,导致插入失败。
2. 正确的解决思路
第一步:必须用预处理语句避免SQL注入
直接把用户输入拼进SQL里是非常危险的操作,改用mysqli_prepare绑定参数,既能防注入,也能避免输入的特殊字符破坏语句。
第二步:确保主表插入成功后,再用正确的主键插入从表
分两种情况处理:
情况A:ID_PAYEMENT是自增主键
- 插入
payement时不要指定这个字段,让数据库自动生成; - 用
mysqli_insert_id($con)获取刚插入的自增主键值,再把这个值作为外键插入payer表。
情况B:ID_PAYEMENT是手动指定的主键
- 先检查这个ID是否已经在
payement表存在(避免主键重复); - 确认第一个插入执行成功后,再执行第二个插入操作。
第三步:用事务保证数据一致性
如果两个插入需要同时成功或同时失败(比如主表插成了但从表没插成,会导致数据不一致),可以用事务把两个操作包裹起来,一旦其中一个失败,就回滚所有操作。
修正后的代码示例
针对自增主键的场景
<?php $InputCIN = filter_input(INPUT_POST,"CIN"); $InputDATE_PAYEMENT = filter_input(INPUT_POST,"DATE_PAYEMENT"); $InputTYPE_DE_PAYEMENT = filter_input(INPUT_POST,"TYPE_DE_PAYEMENT"); $InputPRIX = filter_input(INPUT_POST,"PRIX"); $InputMOIS_PAYEMENT = filter_input(INPUT_POST,"MOIS_PAYEMENT"); if(isset($_POST['butAj2'])) { if(empty($InputCIN)) { $msg = "<div class='alert alert-danger'><span class='fa fa-square-check'></span>请填写CIN</div>"; } else { // 开启事务 mysqli_begin_transaction($con); try { // 插入payement,不指定自增的ID_PAYEMENT字段 $stmt1 = mysqli_prepare($con, "INSERT INTO payement (DATE_PAYEMENT, TYPE_DE_PAYEMENT, PRIX, MOIS_PAYEMENT) VALUES (?, ?, ?, ?)"); mysqli_stmt_bind_param($stmt1, "ssds", $InputDATE_PAYEMENT, $InputTYPE_DE_PAYEMENT, $InputPRIX, $InputMOIS_PAYEMENT); mysqli_stmt_execute($stmt1); // 获取刚生成的自增主键 $lastPayementId = mysqli_insert_id($con); // 插入payer表,用刚获取的主键作为外键 $stmt2 = mysqli_prepare($con, "INSERT INTO payer (CIN, ID_PAYEMENT) VALUES (?, ?)"); mysqli_stmt_bind_param($stmt2, "si", $InputCIN, $lastPayementId); mysqli_stmt_execute($stmt2); // 提交事务,确认所有操作生效 mysqli_commit($con); $msg = "<div class='alert alert-success'><span class='fa fa-square-check'></span>添加成功</div>"; // 关闭预处理语句 mysqli_stmt_close($stmt1); mysqli_stmt_close($stmt2); } catch(Exception $e) { // 回滚事务,撤销所有操作 mysqli_rollback($con); $msg = "<div class='alert alert-danger'><span class='fa fa-square-check'></span>添加失败:" . $e->getMessage() . "</div>" . mysqli_error($con); } } } ?>
针对手动指定主键的场景
<?php $InputCIN = filter_input(INPUT_POST,"CIN"); $InputID_PAYEMENT = filter_input(INPUT_POST,"ID_PAYEMENT"); $InputDATE_PAYEMENT = filter_input(INPUT_POST,"DATE_PAYEMENT"); $InputTYPE_DE_PAYEMENT = filter_input(INPUT_POST,"TYPE_DE_PAYEMENT"); $InputPRIX = filter_input(INPUT_POST,"PRIX"); $InputMOIS_PAYEMENT = filter_input(INPUT_POST,"MOIS_PAYEMENT"); if(isset($_POST['butAj2'])) { if(empty($InputID_PAYEMENT) || empty($InputCIN)) { $msg = "<div class='alert alert-danger'><span class='fa fa-square-check'></span>请填写CIN和ID PAYEMENT</div>"; } else { mysqli_begin_transaction($con); try { // 先检查ID_PAYEMENT是否已存在,避免主键重复 $checkStmt = mysqli_prepare($con, "SELECT ID_PAYEMENT FROM payement WHERE ID_PAYEMENT = ?"); mysqli_stmt_bind_param($checkStmt, "i", $InputID_PAYEMENT); mysqli_stmt_execute($checkStmt); mysqli_stmt_store_result($checkStmt); if(mysqli_stmt_num_rows($checkStmt) > 0) { throw new Exception("该ID_PAYEMENT已存在"); } mysqli_stmt_close($checkStmt); // 插入payement表 $stmt1 = mysqli_prepare($con, "INSERT INTO payement VALUES (?, ?, ?, ?, ?)"); mysqli_stmt_bind_param($stmt1, "issds", $InputID_PAYEMENT, $InputDATE_PAYEMENT, $InputTYPE_DE_PAYEMENT, $InputPRIX, $InputMOIS_PAYEMENT); mysqli_stmt_execute($stmt1); // 插入payer表 $stmt2 = mysqli_prepare($con, "INSERT INTO payer (CIN, ID_PAYEMENT) VALUES (?, ?)"); mysqli_stmt_bind_param($stmt2, "si", $InputCIN, $InputID_PAYEMENT); mysqli_stmt_execute($stmt2); mysqli_commit($con); $msg = "<div class='alert alert-success'><span class='fa fa-square-check'></span>添加成功</div>"; mysqli_stmt_close($stmt1); mysqli_stmt_close($stmt2); } catch(Exception $e) { mysqli_rollback($con); $msg = "<div class='alert alert-danger'><span class='fa fa-square-check'></span>添加失败:" . $e->getMessage() . "</div>" . mysqli_error($con); } } } ?>
关于「设置延迟」的疑问
完全没必要设置延迟!因为mysqli_query是同步执行的,第一个查询执行完成后才会启动第二个。你的问题本质是数据一致性和SQL语句正确性的问题,和执行顺序无关。
内容的提问来源于stack exchange,提问作者medo100000S
相关产品推荐
相关产品推荐

