检查重复条目与使用PDO errorInfo结果

时间:2012-05-27 15:05:24

标签: php mysql pdo

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

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

1)在执行插入之前运行快速SELECT语句。如果select返回结果,请通知用户,不要运行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 个答案:

答案 0 :(得分:55)

您可以使用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处理数据检查的想法。

答案 1 :(得分:9)

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

在你的编码中防守过于容易犯错误。 Python有这样的说法:“要求宽恕比要求许可更容易”,而且这种哲学并不是特定于Python的。

答案 2 :(得分:0)

请注意,如果您有一个AUTO_INCREMENT列并且正在使用InnoDB,那么即使未添加任何新行,失败的INSERT也会增加“下一个自动ID”值。例如,请参阅INSERT ... ON DUPLICATE KEYAUTO_INCREMENT Handling in InnoDB的文档。这会导致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的间隔,那么就无需进行先发制人检查。

答案 3 :(得分:0)

对我来说更简单的方法是根据一系列重复的状态代码检查错误代码。这样您就不必担心PDO返回什么。

Array ( [0] => first [1] => second [2] => to_char(my,(big, and, fancy),house) as bigly [3] => (SELECT myVar,foo from z) as super [4] => export(mastermind and others) as aloah ) 

谢谢! :-)