【问题标题】:MySQL: How to lock tables and start a transaction?MySQL:如何锁定表并启动事务?
【发布时间】:2012-08-22 10:08:35
【问题描述】:

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

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

我必须处理三个问题:

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

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

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

这意味着我需要同时锁定表和开始一个事务。根据 MySQL 手册,this is not possible。启动事务会释放锁,锁定表会提交任何活动事务。

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

【问题讨论】:

    标签: mysql transactions locking data-consistency


    【解决方案1】:

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

    你可以这样做:

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

    欲了解更多信息,请参阅mysql doc

    【讨论】:

    • 这看起来确实有效,但仅适用于 InnoDB 表(如果这是一个问题。)
    【解决方案2】:

    如果是我,我会在 MySQL 中使用advisory locking function 来实现一个互斥锁来更新缓存,以及一个事务来实现读取隔离。例如

    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; 
    }
    

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

    【讨论】:

      【解决方案3】:

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

      为您的缓存数据添加时间戳列。

      当需要更新缓存数据时:

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

      当您需要提供缓存数据时

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

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

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

        • 不幸的是,锁定文件在这里不起作用,因为我们有多个 Web 服务器运行站点文件的本地副本。因此,一台 Web 服务器不会看到另一台服务器创建的锁定文件。我将上次更新时间存储在表中,但在缓存刷新完成之前无法更新 - 否则如果时间更新然后进程被终止(根据第 3 点),数据将不会被已刷新,但上次更新时间会错误地说是。为整个更新锁定该表将中断点 1,阻止其他客户端在刷新期间读取旧数据。
        猜你喜欢
        • 2011-05-12
        • 2016-03-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-23
        • 2011-12-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多