php - 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
对此的最佳解决方案是什么?
谢谢
解决方案
使用“我正在做某事,走开”标签来更新表格的麻烦在于,如果该过程在中间死掉,那么如果不使用更多的 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;
}
任何时候您需要在多个操作中保证数据库的状态,您可能应该将这些操作包含在事务中。
推荐阅读
- javascript - 成功登录后在 SAME 窗口中打开 target.html 页面
- r - ARIMA 函数不输出 AICC、BIC、x 或拟合值。我没有正确格式化时间序列吗?
- java - java.lang.IllegalStateException: 配置错误: 为测试类找到多个@BootstrapWith 声明
- postgresql - 续集关系“喜欢”不存在
- c - 如何为进程间通信创建一个全局窗口?
- database - 如何解决 RSA 解密中的错误“'System.Security.Cryptography.CryptographicException' Bad Data”
- ubuntu - 如何从 ubuntu 18.04 中删除和清除 lam mpi
- batch-file - 你如何制作一个打开一个exe但也“点击”该exe中的运行按钮的批处理文件?
- sql - 使用表达式更改框颜色
- c++ - 模板结构的多个声明,用于获取枚举,描述模板类型