我编写了一个"谁只是"我们后端的小部件。它正在工作,但有时系统会抛出 SQLSTATE [23000]:完整性约束违规:1062重复条目' 42'对于密钥' user_id' MySQL错误。我真的不明白为什么会这样,因为代码在锁定的桌子上运行......
让我们从表结构开始:
--
-- Table structure for table `locktest`
--
DROP TABLE IF EXISTS `locktest`;
CREATE TABLE IF NOT EXISTS `locktest` (
`id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`last_access` datetime NOT NULL,
`path` varchar(20) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Test table for locking test' AUTO_INCREMENT=1 ;
--
-- Indexes for table `locktest`
--
ALTER TABLE `locktest`
ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `user_id` (`user_id`);
这是PHP代码:
$dbh = new \PDO(...);
$dbh->beginTransaction();
$dbh->exec('LOCK TABLES locktest WRITE');
$stmt = $dbh->prepare($sql_update);
$stmt->bindValue(':user_id', $user_id, \PDO::PARAM_INT);
$stmt->bindValue(':last_access', $last_access);
$stmt->bindValue(':path', $path);
$stmt->execute();
$rows_affected = $stmt->rowCount();
if ($rows_affected == 0) {
// New data set
$stmt = $dbh->prepare($sql_insert);
$stmt->bindValue(':user_id', $user_id, \PDO::PARAM_INT);
$stmt->bindValue(':last_access', $last_access);
$stmt->bindValue(':path', $path);
$stmt->execute();
}
$dbh->commit();
$dbh->exec('UNLOCK TABLES');
在此示例中,我使用的是PDO。但我尝试使用mysqli和Zend_Db(使用PDO和mysqli)...它并不重要:并发请求将失败,我不明白为什么。
我注意到如果删除varchar列或将其替换为另一个int列,我就不会看到失败的请求。
此外,当我在代码之前添加sleep(1)
调用时,它也正常工作。看起来像计时问题?不?我真的以为使用LOCKS可以防止这样的错误...
我还尝试了没有TRANSACTIONS的示例,只是为了确保LOCK不会干扰TRANSACTIONS ......没有变化。
我做错了吗?
针对PHP 5.5.13,5.3.28进行了测试。 针对MySQL 5.1.73和5.6.17进行了测试。
是的,我正在使用MyISAM。
我创建了一个小型的完整测试应用程序:https://www.dropbox.com/s/77t9jy596vodmax/locktest.zip
答案 0 :(得分:1)
我自己发现了这个问题:
首先,我的代码没有任何问题。看起来LOCKING不起作用,但确实如此。
问题是,逻辑(当UPDATE不影响任何行时INSERT)不期望智能MySQL服务器:
MySQL似乎发现有时无需更新,因此会将 $ affected_rows = 0 报告回应用程序。
什么时候发生?
想象一下后端用户请求 / $ module / $ action (请求1)。在此示例中, $ module = article 和 $ action = add 。因此,用户可以看到用于添加新数据集的Web表单。如果用户立即提交空表单( / article / check ,请求2),控制器将检测到该表单并将用户重定向回 / article / add (请求3告诉他/她有关所需的字段。
如果在同一时间内发生这种情况,则无法更新请求2和请求3,因为请求1已设置 $ user_id = $ user_id , $ last_access = time()和 $ path = $ module 。
如上所述,这种情况并不经常发生,但如果在相同的时间()内对同一模块进行两次调用,则可能会发生这种情况。
解决问题的两种方法:
感谢所有评论。