PHP/Symfony が同時リクエストを処理する方法、あるいはおそらくデータベース上で潜在的な同時クエリを処理する方法について何かが欠けているはずです...
このコードは不可能を実行しているようです。ランダムに (月に 1 回程度) 新しいエンティティのコピーが下部に作成されます。私の結論は、2 つのクライアントが同じリクエストを 2 回実行し、両方のスレッドが同時に SELECT クエリを実行し、stop == NULL を持つエントリを選択し、その後、両方が (?) そのエントリの停止時間を設定すると、これが発生した場合、両方とも新しいエントリを作成する必要があります。
私の知る限り、これが私の論理的な概要です:
コントローラーの電源が自動的にオフになり、自動的にオンになります
//エントリが夜明け(真夜中)にまたがる場合は、エントリを閉じて、次の日の初めに新しいエントリを開きます プライベート関数 autocloseAndOpen($units) { $now = new \DateTime("now", new \DateTimeZone("UTC")); $repository = $this->em->getRepository('App\Entity\Poslog\Entry'); $query = $repository->createQueryBuilder('e') ->where('e.stop は NULL') ->getQuery(); $results = $query->getResult(); if (!isset($results[0])) { return null; //開いているエントリがまったくありません }$em = $this->em; $messages = ""; foreach ($results as $r) { if ($r->getPosition()->getACRGroup() == $unit) { //ユーザー自身のエントリのみをタッチします $start = $r->getStart(); //日付区切りにまたがるエントリをアサートします $startStr = $start->format("Y-m-d"); //比較に必要です。 $start->format("Y-m-d") が比較句に置かれている場合、PHP はフォーマットの出力ではなく、フォーマットされている datetime オブジェクトを比較します。 $nowStr = $now->format("Y-m-d"); //比較に必要です。 $start->format("Y-m-d") が比較句に置かれている場合、PHP はフォーマットの出力ではなく、フォーマットされている datetime オブジェクトを比較します。 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() 。 「日付区切り (UTC) にわたる位置 (" . $r->getPosition()->getName() . ") にエントリがありました。 「」で自動的に閉じられます。 。 $stop->format("Y-m-d H:i:s") 。 「z。」; $messages .= "" 。 $txt 。 "
modify(' 1 秒'); $entry = 新しいエントリ(); $entry->setStart( $newStartTime ); $entry->setOperator( $r->getOperator() ); $entry->setPosition( $r->getPosition() ); $entry->setStudent( $r->getStudent() ); $em->persist($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 。 "
flush(); //DBに保存 $txt = "新しいエントリが開かれました" 。 $r->getOperator()->getSignature() 。 「同じ位置 (" . $r->getPosition()->getName() . ")"; $messages .= "" 。 $txt 。 "
ここでは、 checkOpenEntries() を使用して追加の検査を実行し、現時点でその位置に stoptime == NULL のストリーム項目が存在するかどうかを確認します。当初、私はこれが冗長であると考えていました。なぜなら、1 つのリクエストがデータベース上で実行および操作されている場合、最初のリクエストが完了するまで別のリクエストは開始されないと考えたからです。
プライベート関数 checkOpenEntries($position,$checkRelatives = false) { $positionsToCheck = 配列(); if ($checkRelatives == true) { $positionsToCheck = $position->getManyPositions(); $positionsToCheck[] = $position; } それ以外 { $positionsToCheck = 配列($position); } //ポジションのオープンエントリーをすべて取得します $repository = $this->em->getRepository('App\Entity\Poslog\Entry'); $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]; // 開いているエントリが 1 つだけの場合、そのオブジェクトを呼び出し元に返す } それ以外 { $body = '位置 ' . $position->getName() . ' で 1 つ以上の開いているログ エントリが見つかりました ' . $position->getACRGroup()->getName() . ' これは不可能であるはずですが、データベースに破損したデータがあるようです。'; $this->email($body); $output['success'] = false; $output['message'] = $body . ' 問題を通知する自動電子メールが ' . $this->globalParameters->get('poslog-email-to') . ' に送信されました。手動検査が必要です。'; $output['logdata'] = null; return $this->prepareResponse($output); } } }この機能を有効にしてやりたいことを実現するには、何らかの「データベースのロック」メソッドを使用する必要がありますか?
すべての機能をテストしましたが、さまざまな状態をシミュレートしたとき (停止時間に NULL を入力するなど、本来であるべきでない場合でも)、すべてが正常に動作しました。ほとんどの場合、すべてがうまくいきますが、月中旬のある日、このようなことが起こります...
順序付け (または暗黙的な排他的アクセス) を保証することはできません。試してみると、自分自身をさらに深く掘り下げることができます。
Matt と KIKO がコメントで述べたように、制約とトランザクションを使用できます。これらはデータベースをクリーンな状態に保つため非常に役立ちますが、アプリケーションは生成されたデータベース層エラーをキャプチャできる必要があることに注意してください。まずは試してみる価値があります。
この問題に対処するもう 1 つの方法は、データベース/アプリケーション レベルのロックを強制することです。
データベース レベルのロックははるかに大雑把で、どこか (長時間実行されるスクリプト内) でロックを解放するのを忘れた場合、非常に許しがたいものになります。
MySQL ドキュメント:
テーブル全体をロックするのは一般的に悪い考えですが、可能です。これはアプリケーションに大きく依存します。
一部の ORM は、デフォルトでオブジェクトのバージョン管理をサポートしており、実行中にバージョンが変更されると例外をスローします。理論的には、アプリケーションで例外が発生し、再試行すると、他のユーザーがすでにそのフィールドに値を設定しており、更新の候補ではなくなっていることがわかります。
アプリケーション レベルのロックより詳細になりますが、コード内のすべてのポイントがロックを尊重する必要があります。そうでない場合は、四角 1 に戻ります。アプリケーションが分散されている場合 (K8S など、または複数のサーバーにデプロイされているだけ)、ロック メカニズムも (インスタンス ローカルではなく) 分散されている必要があります。