检测由于违反唯一约束导致的mysql更新/插入失败


Detect mysql update/insertion failure due to violated unique constraint

这有点类似于这个问题:

MySQL INSERT失败

但是我有不同的想法。假设有一个只有一列的表。列的名称是"title",它有一个唯一的约束。

首先插入一行,其中title = "something"。下次我尝试插入"某些东西"时,由于唯一键约束(这很好),它将失败。我想做的是允许它失败,并检查mysql提供的错误代码,以确保由于唯一键约束而失败。(即让数据库处理唯一性,我只处理错误代码,并在结果返回时告诉用户标题已经存在)。

有办法做到这一点吗?

现在是2015年了,没有什么理由不使用PHP的PDO实现。

恰当的,现代的,"OO"检测和处理由于违反键约束而导致的插入失败的方法如下:

try {
    //PDO query execution goes here.
}
catch ('PDOException $e) {
    if ($e->errorInfo[1] == 1062) {
        //The INSERT query failed due to a key constraint violation.
    }
}

PDOException对象也有许多关于错误的具体性质的更多信息(似乎比人们可能想要或需要的更详细)。

http://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html

http://php.net/manual/en/function.mysql-errno.php

我以前就不得不这样做,而且一点也不好玩:

if( mysql_errno() == 1062) {
    // Duplicate key
} else {
    // ZOMGFAILURE
}

关于编程风格的说明(来自jensgram的回答)
你应该尽量避免使用幻数。相反,您可以将已知的错误码(1062)分配给一个常数(例如MYSQL_CODE_DUPLICATE_KEY)。这将使您的代码更容易维护,因为if语句中的条件在几个月后仍然可读,当1062的含义从记忆中消失时:

我相信重复键的错误代码是1586。如果您尝试执行一个查询,然后在失败时,使用mysql_errno()/mysqli::errno()检查错误代码,并将其与1586进行比较,应该可以做到。如果不是1586,通过在查询后回显错误代码来检查它实际上是什么。

为什么不先执行一个select来查看条目是否已经存在呢?或者通过使用INSERT ON DUPLCATE KEY UPDATE来抑制错误,甚至使用mysql IGNORE关键字。为什么故意造成错误?

是PHP/Mysql用户感兴趣的主题,所以让我概述一个解决方案。请注意

    • 没有神奇的便携方法来做到这一点
    • 的情况不是PHP独有的,如果您想用openJPA检测DB2唯一键约束违反-您必须恢复到类似的处理

假设你有一个表单,其中你有一个字段"Name"

在DB表

添加唯一约束,如-

alter table wb_org add constraint uniq_name unique(name);
2)表单处理程序脚本

表单处理程序脚本应该将数据传递给DB层,如果有任何错误,DB层将其作为dbeexception(由我们定义的异常)发出信号。我们将发送数据到DB层的代码封装在try-catch块中(只显示相关代码)

try{
        .... 
        $organizationDao = new 'com'indigloo'wb'dao'Organization();
        $orgId = $organizationDao->create($loginId,$fvalues["name"]) ;
        ....
    } catch(UIException $ex) {
       ....
       // do UI exception handling
    } catch(DBException $ex) {
        $errors = array();
        $code = $ex->getCode();
        $message = $ex->getMessage();
        // look for code 23000, our constraint name and keyword duplicate 
        // in error message thrown by the DB layer
        // Util::icontains is just case-insensitive stripos wrapper
        if( ($code == 23000)
            && Util::icontains($message,"duplicate")
            && Util::icontains($message,"uniq_name")) {
                $errors = array("This name already exists!");
        } else {
            // Not sure? show generic error
            $errors = array(" Error: doing database operation!") ;
        }
        // log errors
        Logger::getInstance()->error($ex->getMessage());
        Logger::getInstance()->backtrace($ex->getTrace());
        // store data in session to be shown on form page
        $gWeb->store(Constants::STICKY_MAP, $fvalues);
        $gWeb->store(Constants::FORM_ERRORS,$errors);
        // go back to form 
        $fwd = base64_decode($fUrl);
        header("Location: " . $fwd);
        exit(1);

    }catch('Exception $ex) {
       // do generic error handling
    }

请注意,您必须根据您的情况找到ex->getCode()。与上面一样,PDO层实际上将SQLSTATE 23000作为ex->代码返回(其中实际的mysql错误代码是1062)。代码也可能因DB而异。同样的ex->消息也可以有所不同。最好将此检查封装在一个地方,并使用配置文件进行操作。

3) DB层内部(使用PDO)

 static function create($loginId, $name) {
            $dbh = NULL ;
            try {
                $dbh =  PDOWrapper::getHandle();
                //Tx start
                $dbh->beginTransaction();
               ...
               // do DB operations
               //Tx end
                $dbh->commit();
                $dbh = null;
            } catch('Exception $ex) {
                $dbh->rollBack();
                $dbh = null;
                throw new DBException($ex->getMessage(),$ex->getCode());
            }

4)返回到表单(点击form Handler => DB Layer => form Handler错误Handler => form后)

提取会话中设置的错误消息并在表单中显示。

5) dbeexception class

<?php
namespace com'indigloo'exception {
    class DBException extends 'Exception  {
        public function __construct($message,$code=0, 'Exception $previous = null) {
            // PDO exception etc. can return strange string codes
            // Exception expects an integer error code.
            settype($code,"integer");
            parent::__construct($message,$code,$previous);
        }
    }
}
?>

6)包含实用工具方法

 static function icontains($haystack, $needle) {
        return stripos($haystack, $needle) !== false;
    }

我们能在没有异常和PDO的情况下做到这一点吗?

7)没有PDO,只使用mysqli

从mysql获取错误代码和错误消息,并从DB层抛出dbeexception以同样的方式处理dbeexception。

8)我们能做到这一点而不使用异常吗?

我在写这篇文章的时候没有任何在实际代码中实际操作的经验。所以如果你不同意,请让我知道。另外,如果你有更好的计划,请分享。如果你只是想要一个通用的处理程序,那么是的。

  • 在DB层内部:使用trigger_error而不是抛出异常来引发错误。使用MAGIC_STRING + DB_CODE
  • 为表单处理程序页定义自定义错误处理程序
  • 在您的自定义error_handler中的表单处理程序:查找MAGIC_STRING + code
  • 如果你得到MAGIC_STRING +代码然后
    • 在会话
    • 中设置合适的消息
    • 转发到表单页
    • 显示会话
    • 中的自定义消息集

我发现trigger_error和error_handlers的问题是

  • 不能像处理异常那样在执行流中捕获它们。然而,这不是一个问题,在我们的情况下,因为我们的error_handler页面只需要重定向到表单页面。
  • 我不知道如何用trigger_error方法引发特定的错误代码(我想要的代码)。如果有可能引发代码X和我们的消息y的错误就好了。据我所知,你不能这样做。这就是为什么我们要恢复解析error_handler接收到的每个error_message。

我没有太多的错误代码工作经验(我已经提出了异常)-所以也许其他人可以启发我们。

代码样本来自我的公共github仓库https://github.com/rjha/website -代码,我正在编写创建一个网站建设者从同一DB启动数千个网站。上面的代码用于检查网站的唯一名称。

摘自PHP文档关于函数mysql_errno:

Returns the error number from the last MySQL function, 
or 0 (zero) if no error occurred. 

同样,从MySQL文档中约束冲突错误,错误码893对应于:

Constraint violation e.g. duplicate value in unique index

那么,我们可以这样写:

if (!$result) {
    $error_code = mysql_errno();
    if ($error_code == 893) {
        // Duplicate Key
    } else {
        // Some other error.
    }
}

如果您了解一些SQL,请尝试此解决方案(经过测试)

$username = "John";
$stmt = $pdo->prepare("
    INSERT INTO users (
        username
    ) SELECT * FROM (
        SELECT :username
    ) AS compare
    WHERE NOT EXISTS (
        SELECT username 
        FROM users 
        WHERE username = :username
    ) LIMIT 1;
");
$stmt->bindParam(":username", $username);
if ($stmt->execute()) {
    if ($stmt->rowCount() == 0) {
        echo "Dublicate Username, ".$username." already exists.";
    } else {
        echo $username." not in use yet.";
    }
}