我有以下问题。我有一个Web应用程序(用php Yii2编写),预计多个发布请求将在很短的时间内到达应用程序服务器。业务逻辑应该非常严格,这意味着只有第一个请求的数据应该插入MySQL表中,其余的都应该被忽略。客户在发布请求中同时发送了父母的ID和最新的子记录的ID。我以这种方式使用Yii的数据库事务。
$transaction = Yii::$app->db->beginTransaction();
$parent = ObjectParent::findOne(Yii::$app->request->post('parent_id')));
$latest_child = ObjectChild::findOne(Yii::$app->request->post('latest_child_id')));
if($parent->latest_child_id == $latest_child->id) {
try{
$new_child = $latest_child->createNewChild();
$parent->setLatestChild($new_child->id);
$transaction->commit();
} catch{
$transaction->rollback();
}
}
如果请求按顺序获得收入,则第二个请求将被忽略,因为最新的子记录的ID与来自客户端的ID不匹配。但是我的问题是,数据库中插入了多行。数据库的隔离级别是REPEATABLE READ,它应确保(根据我的知识),保证在事务中读取的行在提交之前不会更改。如果这是真的,那么那将不是问题,因为它将使第二笔交易“中断”。问题可能是Yii可能不使用或不知道这些DB锁,因此不知道该记录已是事务的一部分,并根据对象的当前状态进行验证。数据库当然对验证规则一无所知,因此从它的角度来看也很好。
我解决这个问题的想法:
稍后再输入验证逻辑,靠近commitm并在$ parent-> setLatestChildId($ new_child-> id)之后;我不知道这是否是100%的解决方案,所以我不想开始重写经过测试的代码。请注意,上面的框架代码只是原始代码的简化版本。
用数据库触发器解决整个问题,因此它将绕过应用程序上下文。
请让我知道在这些情况下的最佳实践。不幸的是,我在这些并发问题上经验不足,并且很难测试并模拟并发请求。谢谢
可重复只读确保如果您读取事务中的行,则重新读取这些行将得到相同的结果。另一笔交易可能会改变结果。
对它们进行一些锁定是可能的:
SELECT ... [LOCK IN SHARE MODE|FOR UPDATE]
但是,对于确保父母/子女插入物唯一的情况,我建议(parent_id,child_id)
是表中的唯一键或主键,这样重复插入将产生重复键异常。