首页 > 解决方案 > PHP中的同步进程

问题描述

对不起,如果这令人困惑。我将尽我所能把它总结得尽可能干净。

我有一个名为寄售的表。

    CREATE TABLE `consignment` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `consignment_status` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
  `account` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
  `hawb` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
  `print_status` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,PRIMARY KEY (`id`),
  KEY `index2` (`hawb`),
  KEY `index3` (`account`),
  KEY `index4` (`consignment_status`),
) ENGINE=InnoDB AUTO_INCREMENT=841283 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC;

当在此表中输入条目时,我将 print_status 设置为“就绪”。

我将寄售表中的所有记录显示为在 HTML 页面中有效的寄售状态

然后用户选择记录并单击生成按钮以生成一些 pdf。

当 2 个用户打开同一个页面并同时单击生成相同的货物时,就会出现问题。这样就为同一个托运条目生成了 2 个 PDF。

需要解决方案,以便如果一个用户单击了生成 PDF 的按钮,对于另一个用户,代码应该跳过那些寄售条目。

到目前为止,我所做的是在名为 print_status 的数据库中添加了一个额外的列。

当用户单击生成按钮时,执行 MySQL 数据库查询,选择打印状态未激活的记录。

SELECT * FROM consignment c WHERE  c.account='1234'AND c.consignment_status='valid' AND c.print_status='ready' GROUP BY hawb  having count(*)=1 ORDER BY account, hawb
            

选择后立即对相同的记录进行更新以将打印状态设置为活动,以便其他进程无法选择相同的记录。

  update consignment set print_status='ACTIVE' where id in (4564562)

如果 2 个用户同时或接近同时单击生成按钮生成按钮,并且两个进程都能够选择 print_status 为“就绪”的记录,这似乎不起作用

另一个需要注意的问题是,假设用户 A 输入 5 条记录(1,2,3,4,5),用户 B 输入 5 条记录(6,7,8,9,10)。将向两个用户显示记录 (1,2,3,4,5,6,7,8,9,10)。然后用户 A 选择要打印的记录 (1,2,3),用户 B 选择要打印的 (1,2,6,7,8)。所以这种情况下,当用户 A 去打印 PDF 时,他应该只能为 (3) 打印 PDF,而用户 B 应该能够为所有 (1,2,6,7,8) 打印 PDF

对此的最佳解决方案是什么?

谢谢

标签: phpmysql

解决方案


使用“我正在做某事,走开”标签来更新表格的麻烦在于,如果该过程在中间死掉,那么如果不使用更多的 kludge 和几乎无穷无尽的边缘情况,就不会再尝试再次尝试。

这最好通过一个奇怪的技巧来完成:在处理时使用事务来锁定寄售表中的行。

原始 SQL 如下所示:

BEGIN TRANSACTION;
SELECT *
  FROM consignment c
  WHERE
    c.account='1234'
    AND c.consignment_status='valid'
    AND c.print_status='ready'
  GROUP BY hawb HAVING COUNT(*)=1
  ORDER BY account, hawb
  FOR UPDATE;
-- app code executes
UPDATE consignment SET print_status='ACTIVE' WHERE id IN (4564562);
COMMIT;

请注意,FOR UPDATE在选择结束时,这会在事务期间锁定行。参考:https ://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html

PHP 代码可以添加一些花里胡哨的东西。我将在这里假设 PDO,但是如果您使用 MySQLi,这里的所有内容都有等价物,为了简洁起见,我还将剪掉准备/参数调用的主体。

$dbh->beginTransaction();
// assuming that you've set the error mode to exception
try {
    $stmt = $dbh->prepare('SELECT ... FOR UPDATE');
    $stmt->execute([...]);
    // generate the PDF
    if( ! $pdf_success ) {
        // uh oh, everything is terrible.
        throw new MyFunException();
    }
    $stmt = $dbh->prepare('UPDATE consignment ...');
    $stmt->execute([...]);
    $dbh->commit();
} catch(\Exception $e) {
    // you can set multiple catch blocks for different kinds of exceptions
    // immediately roll back the transaction so nothing gets stuck waiting for the lock to release.
    $dbh->rollback();
    // maybe marked the consignment as "ERROR" and/or notify a human for review
    // re-throw the exception, unless you plan to actually handle it here
    throw $e;
}

任何时候您需要在多个操作中保证数据库的状态,您可能应该将这些操作包含在事务中。


推荐阅读