PHP/Symfony가 동시 요청을 처리하는 방법이나 데이터베이스에서 잠재적인 동시 쿼리를 처리하는 방법에 대해 뭔가 빠진 것 같습니다...
이 코드는 불가능한 작업을 수행하는 것 같습니다. 무작위로(약 한 달에 한 번) 하단에 새 엔터티의 복사본을 생성합니다. 내 결론은 두 클라이언트가 동일한 요청을 두 번 만들고 두 스레드가 동시에 SELECT 쿼리를 실행할 때 stop == NULL로 항목을 선택한 다음 둘 다(?) 해당 항목의 중지 시간을 설정한다는 것입니다. 이런 일이 발생하면 둘 다 새 항목을 작성해야 합니다.
내가 아는 한 이것은 나의 논리적 개요입니다.
<올>컨트롤러가 자동으로 꺼졌다가 켜집니다
//입력이 새벽(자정)에 걸쳐 있는 경우 해당 항목을 닫고 다음 날 초에 새 항목을 엽니다. 개인 함수 autocloseAndOpen($units) { $now = new DateTime("현재", new DateTimeZone("UTC")); $repository = $this->em->getRepository('AppEntityPoslogEntry'); $query = $repository->createQueryBuilder('e') ->where('e.stop은 NULL입니다') ->getQuery(); $results = $query->getResult(); if (!isset($results[0])) { return null; //열린 항목이 전혀 없습니다. }$em = $this->em; $메시지 = ""; foreach($결과는 $r로 표시됨) { if ($r->getPosition()->getACRGroup() == $unit) { //사용자 자신의 항목만 터치합니다. $start = $r->getStart(); //datebreak에 걸쳐 항목을 주장합니다. $startStr = $start->format("Y-m-d"); //비교에 필요합니다. $start->format("Y-m-d")이 비교 절에 있으면 PHP는 서식의 출력이 아니라 서식이 지정된 날짜/시간 개체를 계속 비교합니다. $nowStr = $now->format("Y-m-d"); //비교에 필요합니다. $start->format("Y-m-d")이 비교 절에 있으면 PHP는 서식의 출력이 아닌 서식이 지정된 날짜/시간 개체를 계속 비교합니다. if ($startStr < $nowStr) { $stop = new DateTimeImmutable($start->format("Y-m-d")."23:59:59", new DateTimeZone("UTC")); $r->setStop($stop); $em->flush(); $txt = $unit->getName() . " datebreak(UTC)에 걸쳐 위치(" . $r->getPosition()->getName() . ")에 항목이 있습니다. "에 자동으로 종료됩니다. . $stop->format("Y-m-d H:i:s") . "z.";; $messages .= "" . $txt . "
"; //새 항목 열기 $newStartTime = $stop->modify('+1초'); $entry = 새 항목(); $entry->setStart( $newStartTime ); $entry->setOperator( $r->getOperator() ); $entry->setPosition( $r->getPosition() ); $entry->setStudent( $r->getStudent() ); $em->지속($entry); //새 항목을 자동 열기 전에 향후 항목이 없는지 확인 $futureE = $this->checkFutureEntries($r->getPosition(),true); $openE = $this->checkOpenEntries($r->getPosition(), true); if ($futureE !== 0 || $openE !== 0) { $txt = ""에 대한 새 항목을 열려고 했습니다. . $r->getOperator()->getSignature() . " 다음 날 같은 위치(" . $r->getPosition()->getName() . ")에 있지만 충돌하는 항목이 있습니다."; $messages .= "" . $txt . "
"; }또 다른 { $em->flush() //DB에 저장 $txt = "'$r->getOperator()->getSignature()'에 대한 새 항목이 같은 위치에 열렸습니다(" . $r->getPosition()-> getName() ")". $messages .= "" $txt . } } } } $ 메시지를 반환합니다. }
여기서는 checkOpenEntries()를 사용하여 현재 해당 위치에 stoptime == NULL인 항목이 있는지 확인하기 위해 추가 검사를 실행하기도 했습니다. 처음에는 하나의 요청이 데이터베이스에서 실행되고 작동 중이면 첫 번째 요청이 완료될 때까지 다른 요청이 시작되지 않을 것이라고 생각했기 때문에 이것이 중복된다고 생각했습니다.
비공개 함수 checkOpenEntries($position,$checkRelatives = false) { $positionsToCheck = 배열(); if ($checkRelatives == true) { $positionsToCheck = $position->getRelatedPositions(); $positionsToCheck[] = $위치; } 또 다른 { $positionsToCheck = 배열($position); } //위치에 대해 열려 있는 모든 항목을 가져옵니다. $repository = $this->em->getRepository('AppEntityPoslogEntry'); $query = $repository->createQueryBuilder('e') ->where('e.stop은 NULL이고 e.position IN (:positions)') ->setParameter('positions', $positionsToCheck) ->getQuery(); $results = $query->getResult(); if(!isset($results[0])) { return 0; //호출자에게 열린 항목이 없음을 알립니다. } 또 다른 { if (count($results) === 1) { return $results[0]; //열린 항목이 정확히 하나인 경우 해당 객체를 호출자에게 반환합니다. } 또 다른 { $body = ' ' 위치에 대한 열린 로그 항목이 1개 이상 발견되었습니다. ' . $position->getACRGroup()->getName() ' 데이터베이스에 손상된 데이터가 있는 것 같습니다.'; $this->이메일($body); $output['성공'] = 거짓; $output['message'] = $body . ' 문제를 알리기 위해 ' .$this->globalParameters->get('poslog-email-to') '로 자동 이메일이 전송되었습니다. 필수입니다.'; $output['logdata'] = null; return $this->prepareResponse($output); } } }
원하는 작업을 수행하려면 이 기능을 활성화하려면 일종의 "데이터베이스 잠금" 방법을 사용해야 합니까?
모든 기능을 테스트했으며 다양한 상태를 시뮬레이션했을 때(정지 시간에 NULL을 입력하는 등, 그렇지 않은 경우에도) 모든 것이 잘 작동했습니다. 대부분의 경우 모든 것이 잘 작동하지만 월 중순의 어느 날 이런 일이 발생합니다...
순서(또는 암시적 독점 액세스)를 보장할 수 없습니다. 한번 시도해 보시면 점점 더 깊이 파고들게 될 것입니다.
Matt와 KIKO가 댓글에서 언급했듯이 제약 조건과 트랜잭션을 사용할 수 있습니다. 이는 데이터베이스가 깨끗하게 유지되므로 많은 도움이 될 것입니다. 하지만 애플리케이션은 데이터베이스 계층에서 발생하는 실수를 캡처할 수 있어야 한다는 점을 기억하세요.먼저 시도해 볼 가치가 있습니다.
이 문제를 해결하는 또 다른 방법은 데이터베이스/애플리케이션 수준 잠금을 강제하는 것입니다.
데이터베이스 수준 잠금은 (장기 실행 스크립트에서) 어딘가에 잠금을 해제하는 것을 잊었다면 훨씬 더 거칠고 변명의 여지가 없습니다.
MySQL 문서:
테이블 전체를 잠그는 것은 일반적으로 나쁜 생각이지만 가능합니다. 이는 응용 프로그램에 따라 많이 달라집니다.
일부 ORM은 기본적으로 객체 버전 관리를 지원하며 실행 중에 버전이 변경되면 예외가 발생합니다. 이론적으로 애플리케이션에서 예외가 발생하고 다시 시도하면 다른 사람이 이미 필드를 채웠으며 해당 필드는 더 이상 업데이트할 대상이 아니라는 사실을 알게 됩니다.
애플리케이션 수준 잠금은 더 세부적이지만 코드의 모든 지점은 잠금을 준수해야 합니다. 그렇지 않으면 사각형 #1로 돌아갑니다. 애플리케이션이 분산된 경우(예: K8S 또는 여러 서버에 배포된 경우) 잠금 메커니즘도 분산되어야 합니다(인스턴스 로컬이 아님)