【发布时间】:2018-09-06 20:52:20
【问题描述】:
我遇到了使用 MyISAM 引擎的 MySQL 表锁的奇怪问题。
假设我有这种类型的代码:
LOCK TABLES t1 WRITE;
SELECT SQL_NO_CACHE val1 FROM t1 WHERE something; // val1 = old
// some conditions on val1 and logic
UPDATE t1 SET val1 = new WHERE something;
UNLOCK TABLES;
据我所知,这应该可以防止任何并发更新。但事实并非如此。有时它只是忽略锁定,并且在另一个线程将其更改为“新”之后在 val1 中读取“旧”。我什至使用 SQL_NO_CACHE 来防止错误地检索旧数据。
这是为什么呢?我怎样才能确定地防止更新竞争?
谢谢。
MySQL 5.5.28、MyISAM、PHP 5.2、mysql_ 扩展(已过时,但在旧项目上)
编辑:
好的,在纯SQL中不应该发生,所以有PHP代码:
<?php
$conn = mysql_connect(...);
...
mysql_query("LOCK TABLES t1, t2 WRITE"); //t2 also used
$result = mysql_fetch_array(mysql_query("SELECT last_user, ... FROM t1 WHERE id = XXX"));
if($result["last_user"] != $session_user) { //is last activity from another than current user?
DoStuffWithUser(...); //custom function which uses t2 table
mysql_query("UPDATE t1 SET last_user = ".$session_user." WHERE id = XXX");
}
mysql_query("UNLOCK TABLES");
...
?>
结果是,当前用户多次调用 DoStuffWithUser()。
没有特殊的应用程序、驱动程序、框架。只是内置的 PHP 函数。
看来,这个问题主要是(我不能确定是否完全是)当一个用户多次执行操作时 - 双击,一些网络故障,等等。
【问题讨论】:
-
and "old" is read in val1 after another thread changed it to "new"进行这些更新的应用程序是什么?可能驱动程序在做什么缓存? -
这应该是不可能的,虽然可能会发生错误,但这是一个非常古老、经常使用和测试的功能,因此您似乎更有可能在代码中做错了什么,或者它是外部影响(例如自动重新连接?连接池?)。要检查发生了什么,我会尝试在锁之后、val1 之后以及释放锁之前和之后记录到日志表(使用自动增量),并检查是否有一些意外的顺序。
-
我也添加了 php 代码,要清楚...
-
@Solarflare 日志表已完成并经过测试(由我完成,多次单击)。在日志表中,我可以看到我在第一个 LOCK 之后又建立了两个连接,它们能够读取和更新锁定的表 - 所以日志顺序就像 lock1,lock2,select1,select2,update1,update2,dostuff1,lock3,dostuff2,unlock1,选择3,解锁2,解锁3。所以第三个连接没有执行动作,但仍然干扰其他锁......
-
Lock1, lock2应该已经不可能了,会话 2 应该在那里等待,然后才能获得锁。您能否在您的日志中包含connection_id()(以及 val1 的值),以排除您正在重新连接或重新使用相同的连接来处理您假设的不同会话。另外,你能检查一下你的表不是临时表吗? (从上下文来看似乎没有意义,但只是为了确定)。我还假设您没有使用集群/负载平衡(复制)设置或类似的设置。