1

我正在制作一个注册页面。我担心两个用户同时使用相同的用户名注册的并发问题(知道这非常罕见,但讨厌代码中有小缺陷)。

所以我的方法是,检查用户名是否存在,如果不存在,插入一个新行。我正在使用 PDO

我试过的

我正在使用事务,但从我的问题来看,PHP PDO 的事务究竟如何与并发一起工作?看来两个事务可以同时读取

  • 我不认为我可以使用select...for update,因为我没有更新;事实上,我需要锁定一个“虚构”行,如果它不存在,将添加一个新条目
  • 我试过谷歌搜索一些例子,但他们似乎没有处理上面提到的并发问题http://php.about.com/od/finishedphp1/ss/php_login_code_2.htm

解决方案?

我已经用谷歌搜索并UNIQUE在用户名字段上添加了一个约束,但不想依赖 MySQL 错误来回滚事务。我不是专家,但它对我来说并不优雅。那么有没有办法insert if not exists使用纯MySQL?

现在,如果这真的是要走的路,我有几个问题。如果抛出警告,是否会停止查询?就像它会抛出此处示例中所示的异常PHP + MySQL 事务示例?还是只有彻头彻尾的错误会引发异常?

另外,这里似乎暗示我可以使用 PHP 检测和处理 MySQL 警告吗?警告会以某种方式出现在 PHP 输出中,但我从来没有遇到过“可见的 MySQL 错误”。问题不是在我的代码中使用 PDO,但我只是想知道。MySQL 错误和警告是否会生成 html 输出?还是只有在我打电话时才说些什么errorInfo()

谢谢

4

4 回答 4

2

那么,如果纯 MySQL 不存在,有没有办法插入?

最好的方法通常被认为是依赖于 UNIQUE 约束。不过还有其他选择

如果抛出警告,是否会停止查询?

不一定,但在您的特定情况下,尝试插入具有 UNIQUE 约束的副本无论如何都不会通过。

MySQL 错误和警告是否会生成 html 输出?

不,不是自动的,你必须自己处理。如果操作抛出异常并且您设置了异常处理程序,它自然会触发该异常,但也可以对其进行处理。

无论如何,严格来说,尝试在其中一个字段重复且具有 UNIQUE 约束的表中插入条目将永远不会成功,无论错误如何。然后由您决定如何处理错误,但是 PDO 在这些情况下会特别抛出异常,因此它可以很简单:

try {
    //Insert user here

} catch(Exception $e) {
    if(($PDO->errorCode() == 23000) || ($PDOStatement->errorCode() == 23000)) {
        //Duplicate, show friendly error to the user and whatnot
    }
} 

我在这里使用了 SQLSTATE 错误代码 23000,因为它是最常见的重复项,但是您几乎可以检查任何内容,这里是 mysql 的服务器错误代码列表。

于 2012-06-23T18:54:16.233 回答
2

更新

如果我有两个唯一字段,并且不想让任何一个为空(因为我知道我可以将一个表设置为“可空”并有两个查询),有没有办法检查哪一个搞砸了?就像我想让用户知道是否使用了用户名或电子邮件。如果我做一次插入,我不知道哪个失败了

此外,如果我有两个语句(首先插入用户名,检查是否失败,然后对电子邮件尝试相同),我一次只能检测到一个错误;如果用户名失败并回滚,我不能尝试对电子邮件做同样的事情(这将是多余的,因为即使电子邮件存在我也必须检查第一个查询是否失败)编辑:认为它可以如果我使用嵌套的 try...catch 语句,则工作

我想你想多了。您可以为每个错误向用户显示不同的错误,但实际上一次只能检测一个错误,因为 mysql 只会在遇到第一个问题时给您一个错误消息。假设前面的查询(一次插入所有值):

try {
  $stmt = $db->prepare($sql);
  $stmt->execute(array(
    ':username' => $username,
    ':email' => $email,
    ':password' => $password
  ));

  $db->commit();
} catch (PDOException $e) {
  $db->rollBack();
  if($e->getCode() == 23000) {

    // lets parse the message

   if(false !== strpos('email', $e->getMessage())) {
       echo 'Email "'.  $email . '" already exists';
    } else if(false !== strpos('username', $e->getMessage()) {
       echo 'Username "'. $username .'" taken';
    }

  } else {
     // not a dupe key rethrow error
     throw $e;
  }
}

现在的问题是错误消息看起来像:

Duplicate entry 'THE_VALUE_YOU_TRIED_TO_INSERT' for key THE_KEY_NAME

获得更有意义的报告的方法是在创建表时用有意义的东西命名索引,例如:

CREATE TABLE the_table (
   `id` integer UNSIGNED NOT NULL AUTO_INCREMENT
   `username` varchar(30),
   `email` varchar(100),
   `password` varchar(32),
   PRIMARY KEY (`id`),
   UNIQUE KEY `uk_email` (`email`),
   UNIQUE KEY `uk_username` (`username`)
); 

所以现在您的错误消息将如下所示:

Duplicate entry 'THE_VALUE_YOU_TRIED_TO_INSERT' for key uk_username

或者

Duplicate entry 'THE_VALUE_YOU_TRIED_TO_INSERT' for key uk_email

这很容易解析。

这样做的唯一真正替代方法是在插入之前对表进行选择,以确保值不存在。


如果您使用 PDO,您不应该有 PHP 警告...只是异常,如果未捕获将生成标准 php 错误。如果您已display_errors关闭,则不会输出到屏幕,只会输出到错误日志。

我认为没有办法insert if not exists让您重新使用唯一键然后捕获异常:

$db = new PDO($dsn, $user, $pass);
$sql = "INSERT INTO the_table (username, email, etc) VALUES (:username,:email,:password)";

$db->beginTransaction();
try {
  $stmt = $db->prepare($sql);
  $stmt->execute(array(
    ':username' => $username,
    ':email' => $email,
    ':password' => $password
  ));

  $db->commit();
} catch (PDOException $e) {
  $db->rollBack();
  if($e->getCode() == 23000) {
    // do something to notify username is taken
  } else {
     // not a dupe key rethrow error
     throw $e;
  }
}
于 2012-06-23T18:55:31.857 回答
1

确保没有重复项的最佳方法是UNIQUE在用户名字段上使用约束。MySQL 绝对不会插入具有相同值的第二行,因此您不再需要事务。

我不知道为什么这个解决方案看起来不优雅,但在我看来,这是一个非常好的做法,可以大大简化你的工作。如果这是对数据的约束,那么让数据库服务器为您处理该问题是有意义的。

如果您在事务中执行插入查询,如果发生错误,将停止执行,服务器将执行回滚。您还将在 PHP 中得到需要处理的错误。

于 2012-06-23T18:55:57.960 回答
1

让数据库来处理这个,因为它在完成这项任务方面处于有利地位。

使用唯一的约束,但做

insert ignore into users...

http://dev.mysql.com/doc/refman/5.5/en/insert.html

不会触发错误。您可以检查受影响的行以查看是否插入了行。

于 2012-06-23T19:24:42.870 回答