Wie gehen PHP und MySQL mit gleichzeitigen Anfragen um?
P粉037215587
P粉037215587 2023-09-05 12:17:19
0
1
459
<p>Mir muss etwas darüber entgangen sein, wie PHP/Symfony gleichzeitige Anfragen verarbeitet, oder vielleicht, wie potenzielle gleichzeitige Abfragen in der Datenbank behandelt werden ...</p> <p>Dieser Code scheint das Unmögliche zu leisten – er erstellt zufällig (etwa einmal im Monat) eine Kopie der neuen Entität unten. Meine Schlussfolgerung ist, dass, wenn zwei Clients zweimal dieselbe Anfrage stellen und beide Threads gleichzeitig eine SELECT-Abfrage ausführen, ein Eintrag mit stop == NULL ausgewählt wird und dann beide (?) die Stoppzeit dieses Eintrags festlegen Wenn dies geschieht, schreiben beide einen neuen Eintrag.</p> <p>Soweit ich weiß, ist dies meine logische Gliederung: </p> <ol> <li>Alle Einträge mit NULL-Stoppzeit abrufen</li> <li>Durchlaufen Sie die Einträge</li> <li>Nur fortfahren, wenn das Eingabedatum (UTC) vom aktuellen Datum (UTC) abweicht</li> <li>Stellen Sie die Stoppzeit für offene Einträge auf 23:59:59 ein und leeren Sie sie in die Datenbank</li> <li>Neuen Eintrag erstellen, beginnend um 00:00:00 Uhr am nächsten Tag</li> <li>Stellen Sie sicher, dass an diesem Standort keine weiteren offenen Einträge vorhanden sind</li> <li>Stellen Sie sicher, dass an diesem Ort keine zukünftigen Einträge vorhanden sind</li> <li>Nur dies – neue Einträge in die Datenbank löschen</li> </ol> <p>Controller schaltet sich automatisch aus und ein</p> <pre class="brush:php;toolbar:false;">//Wenn der Eintrag über den Tagesanbruch (Mitternacht) hinausgeht, schließen Sie ihn und öffnen Sie einen neuen Eintrag zu Beginn des nächsten Tages private Funktion autocloseAndOpen($units) { $now = new DateTime("now", new DateTimeZone("UTC")); $repository = $this->em->getRepository('AppEntityPoslogEntry'); $query = $repository->createQueryBuilder('e') ->where('e.stop ist NULL') ->getQuery(); $results = $query->getResult(); if (!isset($results[0])) { return null; //es sind überhaupt keine offenen Einträge vorhanden }$em = $this->em; $messages = ""; foreach ($results as $r) { if ($r->getPosition()->getACRGroup() == $unit) { //nur die eigenen Einträge des Benutzers berühren $start = $r->getStart(); //Eintrag über den Datumsbruch hinweg geltend machen $startStr = $start->format("Y-m-d"); //Für den Vergleich erforderlich, wenn $start->format("Y-m-d") in die Vergleichsklausel eingefügt wird, vergleicht PHP weiterhin das zu formatierende Datetime-Objekt, nicht die Ausgabe der Formatierung. $nowStr = $now->format("Y-m-d"); //Für den Vergleich erforderlich, wenn $start->format("Y-m-d") in die Vergleichsklausel eingefügt wird, vergleicht PHP weiterhin das zu formatierende Datetime-Objekt, nicht die Ausgabe der Formatierung. 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() . " hatte einen Eintrag in Position (" . $r->getPosition()->getName() . "), der sich über den Datumswechsel (UTC) erstreckte. Automatisch geschlossen bei " . $stop->format("Y-m-d H:i:s") . „z.“; $messages .= "<p>" . $txt . "</p>"; //Neuen Eintrag öffnen $newStartTime = $stop->modify('+1 Sekunde'); $entry = neuer Eintrag(); $entry->setStart( $newStartTime ); $entry->setOperator( $r->getOperator() ); $entry->setPosition( $r->getPosition() ); $entry->setStudent( $r->getStudent() ); $em->persist($entry); //Stellen Sie sicher, dass keine zukünftigen Einträge vorhanden sind, bevor ein neuer Eintrag automatisch geöffnet wird $futureE = $this->checkFutureEntries($r->getPosition(),true); $openE = $this->checkOpenEntries($r->getPosition(), true); if ($futureE !== 0 || $openE !== 0) { $txt = "Es wurde versucht, einen neuen Eintrag für " zu öffnen. . $r->getOperator()->getSignature() . " an der gleichen Position (" . $r->getPosition()->getName() . ") am nächsten Tag, aber es gibt widersprüchliche Einträge."; $messages .= "<p>" . $txt . "</p>"; }anders { $em->flush(); //in DB speichern $txt = "Ein neuer Eintrag wurde für " $r->getOperator()->getSignature() geöffnet. getName() . ")" $messages .= "<p>" } } } } $messages zurückgeben; }</pre> <p>Ich habe hier sogar checkOpenEntries() verwendet, um eine zusätzliche Prüfung durchzuführen, um zu sehen, ob zu diesem Zeitpunkt an dieser Stelle Einträge mit stoptime == NULL vorhanden sind. Anfangs hielt ich das für überflüssig, weil ich dachte, dass, wenn eine Anfrage läuft und auf der Datenbank arbeitet, eine andere Anfrage erst starten würde, wenn die erste abgeschlossen ist. </p> <pre class="brush:php;toolbar:false;">private Funktion checkOpenEntries($position,$checkRelatives = false) { $positionsToCheck = array(); if ($checkRelatives == true) { $positionsToCheck = $position->getRelatedPositions(); $positionsToCheck[] = $position; } anders { $positionsToCheck = array($position); } //Alle offenen Einträge für die Position abrufen $repository = $this->em->getRepository('AppEntityPoslogEntry'); $query = $repository->createQueryBuilder('e') ->where('e.stop ist NULL und e.position IN (:positions)') ->setParameter('positions', $positionsToCheck) ->getQuery(); $results = $query->getResult(); if(!isset($results[0])) { return 0; //teilt dem Aufrufer mit, dass keine offenen Einträge vorhanden sind } anders { if (count($results) === 1) { return $results[0]; //wenn genau ein offener Eintrag, dieses Objekt an den Aufrufer zurückgeben } anders { $body = 'Mehr als 1 offener Protokolleintrag für Position ' gefunden. $position->getName() ' in ' $position->getName() '. Es scheint beschädigte Daten in der Datenbank zu geben.'; $this->email($body); $output['success'] = false; $output['message'] = $body . ' Eine automatische E-Mail wurde an ' $this->globalParameters->get('poslog-email-to') ' gesendet, um über das Problem zu informieren ist erforderlich.'; $output['logdata'] = null; return $this->prepareResponse($output); } } }</pre> <p>Muss ich eine Art „Datenbank sperren“-Methode verwenden, um diese Funktionalität zu aktivieren und das zu erreichen, was ich tun möchte? </p> <p>Ich habe alle Funktionen getestet und wenn ich verschiedene Zustände simuliere (NULL-Eingabe für Stoppzeiten usw., auch wenn dies nicht der Fall sein sollte), funktioniert alles einwandfrei. Meistens funktioniert alles gut, aber eines Tages mitten im Monat passiert so etwas ... </p>
P粉037215587
P粉037215587

Antworte allen(1)
P粉921165181

您永远无法保证顺序(或隐式独占访问)。尝试一下,你就会把自己挖掘得越来越深。

正如 Matt 和 KIKO 在评论中提到的,您可以使用约束和事务,这些应该会有很大帮助,因为您的数据库将保持干净,但请记住您的应用程序需要能够捕获数据库层产生的错误。 绝对值得首先尝试。

处理此问题的另一种方法是强制数据库/应用程序级别锁定。

数据库级锁定更加粗糙,如果您在某个地方忘记释放锁定(在长时间运行的脚本中),则非常不可原谅。

MySQL 文档:

锁定整个表通常是一个坏主意,但它是可行的。这很大程度上取决于应用程序。

一些开箱即用的 ORM 支持对象版本控制,如果版本在执行过程中发生更改,则会抛出异常。理论上,您的应用程序会遇到异常,重试时会发现其他人已经填充了该字段,并且不再是更新的候选者。

应用程序级锁定更加细粒度,但代码中的所有点都需要遵守锁定,否则,您将回到方#1。如果您的应用程序是分布式的(比如 K8S,或者只是部署在多个服务器上),那么您的锁定机制也必须是分布式的(不是实例本地的)

Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!