
本文介绍在使用 PDO 批量插入数据时,如何在执行 INSERT 前高效检测并避免重复记录,重点讲解基于业务字段(如 med_nom)的预检逻辑、原子性保障方案及实用代码实现。
本文介绍在使用 pdo 批量插入数据时,如何在执行 insert 前高效检测并避免重复记录,重点讲解基于业务字段(如 `med_nom`)的预检逻辑、原子性保障方案及实用代码实现。
在实际开发中,仅靠“先查后插”(Check-then-Insert)存在竞态风险:两次请求可能同时通过检查,导致重复插入。因此,防重的核心原则是:以数据库约束为最终防线,以应用层预检为用户体验优化手段。
✅ 推荐方案:唯一索引 + INSERT IGNORE / ON DUPLICATE KEY UPDATE
首先,在数据库层面为可能重复的字段(如 med_nom 或组合字段 med_id, med_nom)添加唯一索引:
ALTER TABLE medicinetbl ADD UNIQUE KEY uk_med_id_nom (med_id, med_nom);
然后改用安全插入语句,避免异常中断流程:
<?php if (isset($_POST["med_nom"])) {
$connect = new PDO("mysql:host=localhost;dbname=dbs", "username", "password");
$connect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 启用异常模式
$insertQuery = "INSERT INTO medicinetbl (med_dorder, med_id, med_nom, med_qty, med_prc)
VALUES (:med_dorder, :med_id, :med_nom, :med_qty, :med_prc)
ON DUPLICATE KEY UPDATE med_qty = med_qty + VALUES(med_qty)"; // 示例:数量累加
$statement = $connect->prepare($insertQuery);
$successCount = 0;
$duplicateRecords = [];
for ($count = 0; $count execute([
':med_dorder' => $_POST["med_dorder"][$count] ?? '',
':med_id' => $_POST["med_id"][$count] ?? '',
':med_nom' => $_POST["med_nom"][$count] ?? '',
':med_qty' => $_POST["med_qty"][$count] ?? 0,
':med_prc' => $_POST["med_prc"][$count] ?? 0.0
]);
$successCount++;
} catch (PDOException $e) {
if ($e->getCode() == 23000) { // MySQL 重复键错误码
$duplicateRecords[] = [
'med_id' => $_POST["med_id"][$count],
'med_nom' => $_POST["med_nom"][$count]
];
} else {
throw $e; // 其他错误重新抛出
}
}
}
// 返回结构化结果(供前端提示)
if (!empty($duplicateRecords)) {
echo json_encode([
'status' => 'partial_success',
'message' => "成功插入 {$successCount} 条,发现 " . count($duplicateRecords) . " 条重复记录",
'duplicates' => $duplicateRecords
]);
} else {
echo json_encode(['status' => 'success', 'message' => "全部 {$successCount} 条记录插入成功"]);
}
}
?>
⚠️ 注意事项与最佳实践
- 不要依赖纯 PHP 循环重试生成 ID(如原答案中的 rand(1000,9999) 方式):该方式仅适用于主键/标识字段可自定义且无业务含义的场景,且在高并发下效率低、易死锁;对已有业务字段(如药品名称 med_nom)完全不适用。
-
预检仅作辅助提示:若需在提交前实时提示用户哪些数据已存在,可在插入前用 SELECT COUNT(*) 批量校验(注意使用 IN + 参数化查询,避免 SQL 注入):
$placeholders = str_repeat('?,', count($_POST["med_nom"]) - 1) . '?'; $checkStmt = $connect->prepare("SELECT med_nom FROM medicinetbl WHERE med_nom IN ($placeholders)"); $checkStmt->execute($_POST["med_nom"]); $existing = array_column($checkStmt->fetchAll(), 'med_nom'); - 始终启用 PDO::ERRMODE_EXCEPTION:确保数据库错误能被正确捕获和处理。
- 事务增强一致性(可选):对强一致性要求场景,包裹在 beginTransaction() / commit() 中,并配合 ON DUPLICATE KEY UPDATE 或 INSERT ... SELECT ... WHERE NOT EXISTS 实现更精细控制。
综上,真正的防重不是“避免插入”,而是“让重复变得无害且可控”。结合唯一索引、健壮的 SQL 语句与清晰的反馈机制,才能构建高可靠的数据写入流程。










