【问题标题】:Best way to access/update one table from two or more scripts?从两个或多个脚本访问/更新一个表的最佳方法?
【发布时间】:2013-05-04 14:48:28
【问题描述】:

背景

我有一个表,其中包含两个或多个 PHP 脚本将同时访问的数据。数据看起来像这样:

+--------+-------------------+-----------+-----------+
| ID     | Start             |  End      |  Status   |
+--------+-------------------+-----------+-----------+
| 1      | 1000              | 1500      |  0        |
| 2      | 1000              | 1500      |  0        |
| 3      | 1000              | 1500      |  0        |
| 4      | 1000              | 1500      |  0        |
+--------+-------------------+-----------+-----------+

PHP 脚本在启动时执行以下查询:

SELECT * FROM `table` WHERE `Status` = 0 LIMIT 1

PHP 脚本然后执行从 start 值到 end 值(1000 到 1500)的循环。完成后,它将该行的Status 更新为'1'。它会自动重定向到自己并重新启动整个过程。

我面临的挑战是

  1. 如果我想同时运行 2 个或多个相同的 PHP 脚本访问同一个数据库,我如何确保它们访问同一个记录?

  2. 更大的挑战是,如果我想将循环从 start 拆分到 end 到同一 PHP 脚本的多个实例上,即每 100 个值是脚本的一个实例,然后到达1100,将MySQL Start 更新为1100 并重定向到从1101 开始到1200 的同一脚本,依此类推,直到达到End 数字。在达到End 数字后,该记录的Status 将设置为1,并将继续下一条记录。在实际情况下,它从头到尾大约有一百万个计数,并且在循环中执行了一些内存密集型功能,因此我需要将其分块。

我所知道的关于行级锁定的所有信息,都是在最后几分钟里学到的;而且这些似乎都不能从一个脚本中回答多个实例锁定问题。

我应该考虑会话 ID/cookie 吗?你有什么推荐的?

【问题讨论】:

    标签: php mysql


    【解决方案1】:

    我建议:

    1. 使用InnoDB,支持行级锁定:

      ALTER TABLE `table` ENGINE=InnoDB;
      
    2. 使用Status的值来表示“进行中”;更明确一点,让我们使用 MySQL 的 ENUM 类型:

      ALTER TABLE `table`
        ADD status_new ENUM('queued','in progress','complete') AFTER status;
      
      UPDATE `table` SET status_new = CASE status
        WHEN 0 THEN 'queued'
        WHEN 1 THEN 'complete'
      END;
      
      ALTER TABLE `table` DROP status;
      
    3. 在客户端中使用事务:

      $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, FALSE);   // pdo
      $mysqli->autocommit(FALSE);                        // mysqli (OOP)
      mysqli_autocommit($link, FALSE);                   // mysqli (procedural)
      mysql_query('START TRANSACTION');                  // ext/mysql *
      
    4. 使用locking read 获取要处理的行:

      SELECT * FROM `table` WHERE status_new = 'queued' LIMIT 1 FOR UPDATE;
      
    5. 将获取的行的status设置为'in progress'

      UPDATE `table` SET status_new = 'in progress' WHERE ID = ?
      
    6. 提交事务:

      $dbh->commit();                                    // pdo
      $mysqli->commit();                                 // mysqli (OOP)
      mysqli_commit($link);                              // mysqli (procedural)
      mysql_query('COMMIT');                             // ext/mysql *
      
    7. 处理完成后,UPDATE 再次标记记录。

    请注意您正确检测和处理每个阶段出现的任何错误。

    * 请注意,自 2011 年 6 月以来,不建议在新代码中使用 ext/mysql,现在 PHP v5.5 中已弃用。

    【讨论】:

    • 感谢您的详细解答。非常实用!这解决了我们可以锁定一行并将其标记为进行中的第一个挑战。但是对于第二个挑战,当脚本再次加载以处理记录中的下一个块时,我是否传递了具有当前 ID 的 GET 查询字符串?或者我可以将“锁定”转移到下一个实例吗?
    • @Reddox:这取决于您的安全要求。您的建议可以正常工作,前提是错误的GET 字符串(无论是恶意的还是意外的)没有风险(或没有危害)。根据您的要求,您可能会考虑的其他事项包括跟踪数据库或服务器端$_SESSION 变量中的进度程度;您还可以考虑包含某种身份验证令牌,例如查询字符串中的 HMAC。
    • 我必须查找 HMAC。谢谢:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多