【问题标题】:mysql row locking via php通过php锁定mysql行
【发布时间】:2012-04-06 12:53:02
【问题描述】:

我正在帮助一位朋友使用适合他们业务的基于网络的表单。我正试图让它准备好处理多个用户。我已对其进行设置,以便在显示记录以供编辑之前,我使用以下代码锁定记录。

$query = "START TRANSACTION;";
mysql_query($query);
$query = "SELECT field FROM table WHERE ID = \"$value\" FOR UPDATE;";
mysql_query($query);

(好吧,这已经大大简化了,但这就是mysql的精髓)

它似乎没有工作。但是,当我从命令行直接进入mysql时,使用相同的用户登录并执行

START TRANSACTION;
SELECT field FROM table WHERE ID = "40" FOR UPDATE;

我可以有效地阻止网络表单访问记录“40”并获得超时警告。

我尝试过使用 BEGIN 而不是 START TRANSACTION。我曾尝试先执行 SET AUTOCOMMIT=0 并在锁定后启动事务,但我似乎无法从 PHP 代码中锁定该行。因为我可以从命令行锁定行,所以我认为数据库的设置方式没有问题。我真的希望我在阅读中遗漏了一些简单的东西。

仅供参考,我正在 XAMPP 版本 1.7.3 上进行开发,该版本具有 Apache 2.2.14、MySQL 5.1.41 和 PHP 5.3.1。

提前致谢。这是我第一次发帖,但我过去从这个网站收集了很多知识。

【问题讨论】:

  • 第二次 mysql_query($query); 之后你做了什么?如果您的 php 脚本退出,则锁定释放

标签: php mysql locking row


【解决方案1】:

这是一个古老的讨论,但也许人们仍在关注它。我使用类似于 JMack 的方法,但在我想要行锁定的表中包含锁定信息。我的新专栏是 LockTime 和 LockedBy。要尝试锁定,我会这样做:

UPDATE table
SET LockTime='$now',LockedBy='$Userid'
WHERE Key='$value' AND (LockTime IS NULL OR LockTime<'$past' OR LockedBy='$Userid')

($past 是 4 分钟前)

如果失败,其他人拥有锁。我可以如下显式解锁或让我的锁过期:

UPDATE table
SET LockTime=NULL,LockedBy=''
WHERE Key='$value' AND LockedBy='$Userid'

cron 作业可以删除旧锁,但这并不是必需的。

【讨论】:

    【解决方案2】:

    问题是mysql命令。您可以为此使用 mysqli

    http://nz.php.net/manual/en/class.mysqli.php

    或 PDO。此处描述:

    PHP + MySQL transactions examples

    HTH

    【讨论】:

    • 我同意 webbiedave。请让我知道您为什么这样做以及如何改进,这将对您、我和社区有所帮助。谢谢!
    【解决方案3】:

    问题不在于代码的语法,而在于您尝试使用它的方式。

    就在显示记录以供编辑之前,我正在使用以下代码锁定记录

    据此,我假设您选择并“锁定”该行,然后向您的用户显示该编辑页面,然后当他们提交更改时,它会保存并“解锁”表格。根本问题就在这里。页面加载完成后,PHP 退出并关闭 MySQL 连接。发生这种情况时,所有锁都会立即释放。这就是为什么控制台的行为似乎与您的 PHP 不同的原因。控制台中的等价物是您退出程序。

    您不能长时间锁定表格行以进行编辑。这不是他们的设计。如果要锁定记录以进行编辑,则需要在另一个表中跟踪这些锁定。创建一个名为“edit_locks”的新表,并存储被锁定的记录 id、编辑的用户 id 和锁定时间。当要打开一条记录进行编辑时,锁定整个edit_locks表,查询该记录是否被其他人锁定。如果不是,则插入您的锁定记录,如果是,则显示锁定错误。当用户保存或取消时,从edit_locks中删除锁定记录。如果你想让事情变得简单,只要你的程序想要使用这个表就锁定它。这将帮助您避免竞争条件。

    还有一种情况可能会导致问题。如果用户打开一条记录进行编辑,然后关闭浏览器而不保存或取消,则编辑锁定将永远保留在那里。这就是为什么我说存储它被锁定的时间。编辑器本身应该每 2 分钟左右进行一次 AJAX 调用,以说“我仍然需要锁!”。当 PHP 程序收到这个“重新锁定”请求时,它应该搜索锁,然后将时间戳更新为当前。这样,锁上的时间戳总是在 2 分钟内是最新的。您还需要创建另一个程序来删除旧的过时锁。这应该每隔几分钟在一个 cron 作业中运行。它应该搜索时间戳超过 5 分钟左右的所有锁,然后将其删除。如果时间戳比这更早,那么显然编辑器已经关闭了一些方式,或者时间戳将在 2 分钟内更新。

    就像其他人提到的那样,您应该尝试使用mysqli。它代表“MySQL 改进”,是旧界面的替代品。

    【讨论】:

    • 这种方式有一些优点,但也有一些缺点。您需要不时清理您的锁定/解锁表。这是由崩溃或意外中断引起的。我个人不会使用它。
    • 好的,JMack,你现在已经扩展了你的答案,并在你的第二部分解释了缺点。可靠的答案+1
    • @Andreas 我的描述中包含了一种清理锁定表的方法,这样任何浏览器崩溃都会导致记录锁定不超过几分钟。
    • @Andreas 哎呀,我们有点不同步了。看起来我们在同一个页面上。
    • 非常感谢。你肯定碰到了我的问题。所以如果你需要自己实现整个锁定方案,“SELECT ... FOR UPDATE”有什么用吗?
    【解决方案4】:

    你不应该使用 mysql api,因为它很容易出错,启用 sql-injections 等,并且它缺乏一些功能。我怀疑事务就是其中之一,因为如果我没记错的话,每个查询都是“自行”发送的,而不是在更大的上下文中发送的。

    然而,解决方案是使用其他一些 api,我更喜欢 mysqli,因为它与 mysql 非常相似并且被广泛支持。您也可以轻松地重写代码以使用 mysqli。

    对于事务功能,将 auto-commit 设置为 false 并在需要时自行提交。这与启动和停止事务相同。

    参考看看:

    http://www.php.net/manual/en/mysqli.autocommit.php

    http://www.php.net/manual/en/mysqli.commit.php

    【讨论】:

      【解决方案5】:

      为此(以及所有数据库操作)使用 PDO:

      $value = 40;
      
      try {
          $dbh = new PDO("mysql:host=localhost;dbname=dbname", 'username', 'password');
      } catch (PDOException $e) {
          die('Could not connect to database.');
      }
      
      $dbh->beginTransaction();
      
      try {
          $stm = $dbh->prepare("SELECT field FROM table WHERE ID = ? FOR UPDATE");
          $stm->execute(array($value));
          $dbh->commit();
      } catch (PDOException $e) {
          $dbh->rollBack();
      }
      

      如果你必须使用过时的 mysql_* 函数,你可以这样做:

      mysql_query('SET AUTOCOMMIT=0');
      mysql_query('START TRANSACTION');
      mysql_query($sql);
      mysql_query('SET AUTOCOMMIT=1');
      

      【讨论】:

      • 不要默默地投票。发表评论以帮助获得更好的答案。
      • 作为记录,我认为这个建议没有问题,它是前瞻性的!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-30
      • 2016-12-01
      相关资源
      最近更新 更多