5

TL;DR - MySQL 不允许您同时锁定表和使用事务。有没有办法解决?

我有一个 MySQL 表,用于缓存来自(慢)外部系统的一些数据。该数据用于显示网页(用 PHP 编写)。每隔一段时间,当缓存的数据被认为太旧时,其中一个 Web 连接应该触发缓存数据的更新。

我必须处理三个问题:

  • 在我更新缓存数据时,其他客户端会尝试读取缓存数据
  • 多个客户端可能认为缓存数据太旧并尝试同时更新它
  • 执行工作的 PHP 实例随时可能意外终止,数据不应损坏

我可以通过使用事务来解决第一个和最后一个问题,因此客户端将能够读取旧数据,直到事务提交,他们将立即看到新数据。任何问题只会导致事务回滚。

我可以通过锁定表来解决第二个问题,这样只有一个进程有机会执行更新。当任何其他进程获得锁定时,他们会意识到他们已经被打败了,不需要更新任何东西。

这意味着我需要锁定表启动事务。根据 MySQL 手册,这是不可能的。启动事务会释放锁,锁定表会提交任何活动事务。

有没有办法解决这个问题,还是有另一种方法可以完全实现我的目标?

4

4 回答 4

5

这意味着我需要锁定表并启动事务

您可以这样做:

SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
... do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES;

有关详细信息,请参阅mysql 文档

于 2014-02-07T15:13:52.783 回答
4

如果是我,我会使用 MySQL 中的咨询锁定功能来实现用于更新缓存的互斥锁和用于读取隔离的事务。例如

begin_transaction(); // although reading a single row doesnt really require this
$cached=runquery("SELECT * FROM cache WHERE key=$id");
end_transaction();

if (is_expired($cached)) {
   $cached=refresh_data($cached, $id);
}
...

function refresh_data($cached, $id)
{
 $lockname=some_deterministic_transform($id);
 if (1==runquery("SELECT GET_LOCK('$lockname',0)") {
    $cached=fetch_source_data($id);
    begin_transaction();
    write_data($cached, $id);
    end_transaction();
    runquery("SELECT RELEASE_LOCK('$lockname')");
 }
 return $cached; 
}

(顺便说一句:如果您尝试使用持久连接,可能会发生不好的事情)

于 2012-08-22T11:40:59.110 回答
2

我建议通过完全消除争用来解决问题。

将时间戳列添加到缓存数据。

当您需要更新缓存数据时:

  • 只需使用当前时间戳将新的缓存数据添加到您的表中
  • 删除早于 24 小时的缓存数据。

当您需要提供缓存数据时

  • 按时间戳(DESC)排序并返回最新的缓存数据

在任何给定时间,您的客户都将检索从未被任何其他进程删除的记录。此外,您不关心客户端是否获取属于不同写入的缓存数据(即具有不同时间戳)

于 2020-11-16T08:29:57.920 回答
0

第二个问题完全不用数据库就可以解决。为缓存更新过程设置一个锁定文件,以便其他客户端知道有人已经在上面了。这可能无法捕捉到每一个极端情况,但如果两个客户端同时更新缓存,这有什么大不了的吗?毕竟,他们在事务中对缓存的更新仍然会保持一致。

您甚至可以通过将上次缓存更新时间存储在表中来自己实现锁定。当客户端想要更新缓存时,使其锁定该表,检查最后更新时间,然后更新字段。

即,实现您自己的锁定机制,以防止多个客户端更新缓存。交易将负责其余的工作。

于 2012-08-22T10:59:11.127 回答