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

Posted

技术标签:

【中文标题】检查重复条目与使用 PDO errorInfo 结果【英文标题】:check for duplicate entry vs use PDO errorInfo result 【发布时间】:2012-06-02 05:56:57 【问题描述】:

我有一个 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 为你检查数据会更好”。

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

【问题讨论】:

你有没有想过使用Exception 在什么方面?我当然会处理异常,但除非我遗漏了某些东西,否则异常处理超出了我的问题范围。 【参考方案1】:

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

【讨论】:

PDOException->errorInfo[1] 也是 MySQL 特有的——根据 php 文档,它是 Driver-specific error code。您需要再次检查PDOException->errorInfo[0],这是 SQLSTATE 代码,它应该或多或少(尽管通常不太)可移植。 @lanzz 只检查 $e->getCode() 的结果不是更好吗? @aron.duby: 是的,但是就像许多其他灾难性的 PHP 文档一样,PDOException::getCode() 方法实际上完全没有文档记录,并且这个功能在主要的 PDOException 类文档中被奇怪地描述了,在 protected $code 属性的描述中(从 PDOException 类之外完全无法访问,因此没有人会尝试查找有关它的文档)。 但是使用$e->errorCode() === 23000而不是errorInfo[1]有什么问题? 我必须将 PDOException 更改为 \PDOException 才能使其正常工作。【参考方案2】:

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

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

【讨论】:

【参考方案3】:

请注意,如果您有一个 AUTO_INCREMENT 列并且您正在使用 InnoDB,那么一个失败的 INSERT 确实会增加“下一个自动 id”值,尽管没有添加新行。例如,参见 INSERT ... ON DUPLICATE KEY 和 AUTO_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;
        
    

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

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

【讨论】:

【参考方案4】:

对我有用的更简单的方法是根据一组重复的状态代码检查错误代码。这样您就不必担心 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
   

谢谢! :-)

【讨论】:

【参考方案5】:

我没有使用代码检查,而是使用了这样的 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());


【讨论】:

以上是关于检查重复条目与使用 PDO errorInfo 结果的主要内容,如果未能解决你的问题,请参考以下文章

pdo 中的 errorinfo 函数总是在 medoo lib 中返回 0000

PDO 返回最高整数值且没有重复条目

如何使用 pdo 检查表是不是存在 [重复]

在 symfony2 中处理 1062 重复条目异常

pdo知识点1

PHP的PDO