PDO';MySQL的lastInsertId是一个竞争条件


PDO's lastInsertId for MySQL a race condition?

我正在编写一个PHP类文件,该文件使用PDO将数据推送到MySQL数据库。从本质上讲,该文件很快就会被多次命中(每次都创建一个新的类实例),而lastInsertId()方法无法跟上。例如:

//sleep(rand(100,1000)/100);
$sql = "INSERT INTO `testing` (`name`, `timestamp`) VALUES (?, ?)";
$this->dbh->beginTransaction();
$sth = $this->dbh->prepare($sql);
$sth->bindValue(1, $_POST["name"]);
$sth->bindValue(2, microtime());
$sth->execute();
$this->id = $this->dbh->lastInsertId();
$this->dbh->commit();

如果页面被快速调用两次,当返回$this->id时,两个实例的值都为2,尽管DB看起来是这样的:

+----+--------+-----------------------+
| id | name   | timestamp             |
+----+--------+-----------------------+
|  1 | Mark   | 0.98705900 1385770566 |
|  2 | George | 0.99367300 1385770566 |
+----+--------+-----------------------+

问题是,执行的第一个查询的id值应为1,而执行的第二个查询的id值应为2。为了解决这个问题,我添加了一个随机睡眠(如上所述),它纠正了这个问题。我正在使用交易,我相信这将纠正这个问题。我是不是遗漏了一些显而易见的东西?

对于那些好奇的人,以下是我的表格设置:

CREATE TABLE `testing` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(255) NOT NULL,
    `timestamp` varchar(255) NOT NULL,
     PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

PHP 5.3.3
MySQL 5.1.69

MySQL不会将一个会话的最后一个插入id返回给另一个会话。

http://dev.mysql.com/doc/refman/5.6/en/information-functions.html#function_last-插入id显示:

生成的ID在服务器中按每个连接进行维护。这意味着函数返回给给定客户端的值是为该客户端影响AUTO_INCREMENT列的最新语句生成的第一个AUTO_INCREMENT值。此值不会受到其他客户端的影响,即使它们生成自己的AUTO_INCREMENT值也是如此。这种行为确保每个客户端都可以检索自己的ID,而不必关心其他客户端的活动,也不需要锁或事务。


回复您的意见:

这是MySQL从一开始就有的行为。如果最后一个插入ID容易受到竞争条件的影响,那么返回它将是非常无用的,也就是说,如果其他会话中的插入可能会污染您的会话。

一种可能是您使用的是持久连接,因为旧版本的PHP有一个错误,即可以向新的PHP请求提供连接,并从以前的PHP请求授予对会话范围状态的访问权限。换句话说,像锁、事务、临时表、用户变量和最后一个插入id这样的东西可以保存到后续的PHP请求。这些问题应该在PHP 5.3中使用mysqlnd驱动程序来解决;持久连接应该"重置"为初始状态。

另一种可能的解释是,它确实运行正常,而你的观察是错误的。因此,我建议仔细而有条理地进行测试。

更新:根据您的回答,这个问题与MySQL、PDO或lastInsertId无关。听起来你根本没有看到PHP代码输出的差异,你在Chrome开发工具中看到了网络性能统计数据的意外数字。

问题是Chromium内置控制台的网络选项卡没有显示正确的信息。通过console.log()显示ID可以按预期工作,使用FireBug for Firefox可以按预期显示网络活动。我的工作假设是网络选项卡将显示实际的网络活动,但它并不总是这样。