38

我有一个 MySQL 表,其中有一个定义为唯一的电子邮件地址字段。对于此示例,假设我的表单所做的只是允许用户将他们的电子邮件地址插入表中。

由于电子邮件字段是唯一的,因此如果他们尝试输入相同的电子邮件两次,则查询应该会失败。我很好奇这两种情况之间的权衡:

SELECT1)在执行插入之前运行一个快速语句。如果选择返回结果,通知用户,不要运行该INSERT语句。

2)运行INSERT语句,并检查重复输入错误

// snippet uses PDO
if (!$prep->execute($values))
{
    $err = $prep->errorInfo();
    if (isset($err[1]))
    {
        // 1062 - Duplicate entry
        if ($err[1] == 1062)
            echo 'This email already exists.';
    }
}

此外,请假设正常使用,这意味着重复条目应该是最少的。因此,在第一种情况下,您显然需要为每个插入运行额外的查询,而在第二种情况下,您依赖于错误处理。

另外,我很想听听关于编码风格的想法。我的心在说‘做一个防御型程序员!插入前检查数据!当我的大脑说“嗯,也许让 MySQL 为你检查数据会更好”。

编辑- 请注意,这不是一个“我该怎么做”的问题,而是一个“我为什么要以特定的方式做这个”的问题。我包含的小代码片段有效,但我很好奇的是解决问题的最佳方法。

4

5 回答 5

63

您可以使用 try catch 块执行此操作:

try {
   $prep->execute($values);
   // do other things if successfully inserted
} catch (PDOException $e) {
   if ($e->errorInfo[1] == 1062) {
      // duplicate entry, do something else
   } else {
      // an error other than duplicate entry occurred
   }
}

您还可以研究诸如“INSERT IGNORE”和“INSERT ... ON DUPLICATE KEY UPDATE”之类的替代方案-尽管我认为这些是 MySQL 特定的,并且会违背使用 PDO 的可移植性,如果您担心的话.

编辑:为了更正式地回答您的问题,对我来说,完全使用的解决方案#1(防御性程序员)首先有效地消除了唯一约束的点。所以我同意你让 MySQL 负责数据检查的想法。

于 2012-05-27T15:41:35.313 回答
11

INSERT+ 检查状态应该是更好的方法。使用SELECT+您可以让另一个线程在 the和 theINSERT之间插入相同的值,这意味着您还需要将这两个语句包装在表锁中。SELECTINSERT

在编码中过多的防御很容易出错。Python 有句谚语“请求宽恕比请求许可更容易”,这种哲学并不是真正特定于 Python 的。

于 2012-05-27T16:10:55.557 回答
4

请注意,如果您有一个 AUTO_INCREMENT 列并且您正在使用 InnoDB,那么一个失败的 INSERT 确实会增加“下一个 auto-id”值,尽管没有添加新行。例如,请参阅 InnoDB 中的INSERT ... ON DUPLICATE KEYAUTO_INCREMENT 处理的文档。这会导致 AUTO_INCREMENT id 出现空白,如果您认为自己可能会用完 id,这可能是一个问题。

因此,如果您希望尝试插入已经存在的行是常见的,并且您希望尽可能避免 AUTO_INCREMENT id 中的间隙,您可以同时进行先发制人检查和异常处理:

$already_exists = false;
$stmt = $pdo->prepare("SELECT id FROM emails WHERE email = :email");
$stmt->execute(array(':email' => $email));
if ($stmt->rowCount() > 0) {
    $already_exists = true;
}
else {
    try {
        $stmt = $pdo->prepare("INSERT INTO emails (email) VALUES (:email)");
        $stmt->execute(array(':email' => $email));
    } catch (PDOException $e) {
        if ($e->errorInfo[1] == 1062) {
            $already_exists = true;
        } else {
            throw $e;
        }
    }
}

如果我们确定电子邮件已经存在,第一个查询确保我们不会尝试插入电子邮件。如果电子邮件似乎还不存在,则第二个查询尝试插入电子邮件。我们仍然需要在第二个查询中检查异常,因为在不太可能的竞争条件(多个客户端或线程并行运行上面的代码片段)中仍然可能出现重复。

这种方法使代码健壮,同时仍然避免在 AUTO_INCREMENT id 中创建间隙,除非在极少数情况下竞争条件。如果尝试插入现有电子邮件比尝试插入新电子邮件更常见,这也是最快的方法。如果尝试插入现有电子邮件的情况很少见,并且您不关心 AUTO_INCREMENT id 中的空白,则无需进行先发制人的检查。

于 2019-02-27T13:44:08.217 回答
1

对我有用的一种更简单的方法是根据一组重复的状态代码检查错误代码。这样你就不必担心 PDO 返回什么。

$MYSQL_DUPLICATE_CODES=array(1062,23000);
try {
   $prep->execute($values);
   // do other things if successfully inserted
} catch (PDOException $e) {
   if (in_array($e->getCode(),$MYSQL_DUPLICATE_CODES)) {
      // duplicate entry, do something else
   } else {
      // an error other than duplicate entry occurred
   }
}

谢谢!:-)

于 2019-04-11T11:25:17.647 回答
0

我没有检查代码,而是使用了这样的 catch 块来捕获主键重复条目,并使用该错误代码:1062 Duplicate entry

catch (PDOException $e) {

  if(str_contains($e, '1062 Duplicate entry')) {
       header("Location: login.php");
  }
die("Error inserting user details into database: " .  $e->getMessage());

}
于 2021-10-15T11:48:11.570 回答