【发布时间】:2020-12-24 12:42:47
【问题描述】:
提前感谢您尝试帮助我解决此问题。
我正在使用 CakePHP 2 (2.10.22)。 我有一个创建应用程序的系统。创建的每个应用程序都有一个唯一的应用程序编号。存储此应用程序编号的 MySQL 数据库列设置为“非空”和“唯一”。我正在使用 CakePHP 从数据库中获取最后使用的应用程序编号,然后为需要创建的新应用程序构建下一个应用程序编号。当在给定时间点收到单个请求时,我编写的过程没有任何问题。当同时收到两个创建应用程序的请求时,就会出现问题。我观察到的行为是首先获取的请求获取最后一个应用程序编号 - 例如ABC001233 并将 ABC001234 分配为它需要创建的新应用程序的应用程序编号。它成功地将这个应用程序保存到数据库中。并发运行的第二个请求也将 ABC001233 作为最后一个应用程序编号,并尝试以 ABC001234 作为应用程序编号创建一个新应用程序。 MySQL 数据库返回一个错误,指出应用程序编号不是唯一的。然后我将第二个请求休眠 2 秒,此时第一个应用程序已成功保存到数据库中。然后我重新尝试应用程序创建过程,该过程首先获取最后一个应用程序编号,该编号应该是 ABC001234,但即使第一个请求早已完成,每个数据库读取都会继续返回 ABC001233。两个请求在控制器中都有事务。我注意到的是,当我删除这些事务时,该过程正常工作,对于第一次尝试失败后的第二次请求,第二次尝试正常工作,因为系统正确获取 ABC001234 作为最后一个应用程序编号并将 ABC001235 分配为新应用程序数字。我想知道我需要做什么才能确保即使使用控制器中的事务指令也能正常工作。
请在下面找到有关代码结构的一些基本信息 -
数据库
The last application number is ABC001233
控制器文件
function create_application(){
$db_source->begin(); //The process works correctly if I remove this line.
$result = $Application->create_new();
if($result === true){
$db_source->commit();
)else{
$db_source->rollback();
}
}
应用模型文件
function get_new_application_number(){
$application_record = $this->find('first',[
'order'=>[
$this->name.'.application_number DESC'
],
'fields'=>[
$this->name.'.application_number'
]
]);
$old_application_number = $application_record[$this->name]['application_number'];
$new_application_number = $old_application_number+1;
return $new_application_number;
}
以上是我觉得问题所在。对于获取的第一个请求,此查找正确地发现 ABC001233 是最后一个申请号,然后此函数返回 ABC001234 作为下一个申请号。对于第二个请求,它还会选择 ABC001233 作为最后一个应用程序编号,但是当它尝试将 ABC001234 作为应用程序编号保存时会失败,因为第一个请求已经保存了具有该编号的应用程序。作为第二次请求的第二次尝试的一部分(由于 do/while 循环而发生),再次请求此查找,但不是返回 ABC001234 作为最后一个应用程序编号(根据第一个请求的成功保存),它不断返回 ABC001233 导致无法正确保存。如果我从控制器中删除事务,那么它将在第二次尝试中返回 ABC001234 的地方正常工作。我找不到任何文档说明为什么会这样以及可以做些什么以及我需要一些帮助的地方。谢谢!
function create_new(){
$new_application_number = $this->get_new_application_number();
$save_attempts = 0;
do{
$save_exception = false;
try{
$result = $this->save([$this->name=>['application_number'=>$new_application_number]], [
'atomic'=>false
]);
}catch(Exception $e){
$save_exception = true;
sleep(2);
$new_application_number = $this->get_new_application_number();
}
}while($save_exception === true && $save_attempts++<5);
return !$save_exception;
}
【问题讨论】:
-
是否有某种原因您没有在该 ID 列上使用
auto increment?它旨在准确处理这种情况。 -
嗨@GregSchmidt - 目前的原因是应用程序编号列是一个varchar,它存储了ABC001234之类的东西。所以不能直接使用自动增量。我已经更新了描述以反映相同的内容。
-
在这种情况下,您可能需要使用表锁定来确保一次只有一个进程可以访问此表。锁定它、读取最后一个 ID、保存新记录并解锁它,所有这些都尽可能快。
-
你好@GregSchmidt - 谢谢。 CakePHP 中是否有本地方法来锁定表?我找不到任何文件可以这样做。但是为什么当我不使用事务时它会起作用?当第二个请求开始时,为什么它不能读取第一个请求创建的应用程序号,即使它已经成功保存并且它的事务已经提交?如果可能的话,也希望得到这种理解。谢谢!
-
如果第一个请求已经提交了它的事务,那么第二个请求应该能够读取它。问题是当第二个请求在第一个请求提交之前开始并读取时。不要认为有任何锁定表的内置功能,只需
->execute()那个语句。
标签: mysql cakephp transactions cakephp-2.0 atomic