Comment PHP et MySQL gèrent-ils les requêtes simultanées ?
P粉037215587
P粉037215587 2023-09-05 12:17:19
0
1
566
<p>Il me manque quelque chose sur la façon dont PHP/Symfony gère les requêtes simultanées, ou peut-être sur la manière de gérer les requêtes simultanées potentielles sur la base de données...</p> <p>Ce code semble faire l'impossible : il crée de manière aléatoire (environ une fois par mois) une copie de la nouvelle entité en bas. Ma conclusion est que lorsque deux clients font la même demande deux fois et que les deux threads exécutent une requête SELECT en même temps, sélectionnent une entrée avec stop == NULL, puis ils définissent tous les deux (?) l'heure d'arrêt de cette entrée, cela doivent Lorsque cela se produit, ils écrivent tous les deux une nouvelle entrée.</p> <p>Pour autant que je sache, voici mon schéma logique : </p> <ol> <li>Obtenir toutes les entrées avec une heure d'arrêt NULL</li> <li>Parcourir les entrées</li> <li>Continuer uniquement si la date de saisie (UTC) est différente de la date actuelle (UTC)</li> <li>Définissez l'heure d'arrêt des entrées ouvertes sur 23:59:59 et videz-les dans la base de données</li> <li>Créer une nouvelle entrée à partir de 00:00:00 le lendemain</li> <li>Affirmer qu'il n'y a aucune autre entrée ouverte à cet emplacement</li> <li>Affirmer qu'il n'y a aucune entrée future à cet emplacement</li> <li>Uniquement ceci - vider les nouvelles entrées dans la base de données</li> </ol> <p>Le contrôleur s'éteint et se rallume automatiquement</p> <pre class="brush:php;toolbar:false;">//si l'entrée dure l'aube (minuit), fermez-la et ouvrez une nouvelle entrée au début du jour suivant fonction privée autocloseAndOpen ($ unités) { $maintenant = new DateTime("maintenant", new DateTimeZone("UTC")); $repository = $this->em->getRepository('AppEntityPoslogEntry'); $query = $repository->createQueryBuilder('e') ->où('e.stop est NULL') ->getQuery(); $results = $query->getResult(); si (!isset($results[0])) { return null ; //il n'y a aucune entrée ouverte }$em = $this->em; $messages = ""; foreach ($résultats comme $r) { if ($r->getPosition()->getACRGroup() == $unit) { //ne touche que les propres entrées de l'utilisateur $start = $r->getStart(); //Assert une entrée couvrant la datebreak $startStr = $start->format("Y-m-d"); // Nécessaire pour la comparaison, si $start->format("Y-m-d") est mis dans la clause de comparaison, PHP comparera toujours l'objet datetime en cours de formatage, pas la sortie du formatage. $nowStr = $now->format("Y-m-d"); // Nécessaire pour la comparaison, si $start->format("Y-m-d") est mis dans la clause de comparaison, PHP comparera toujours l'objet datetime en cours de formatage, pas la sortie du formatage. si ($startStr < $nowStr) { $stop = new DateTimeImmutable($start->format("Y-m-d")."23:59:59", new DateTimeZone("UTC")); $r->setStop($stop); $em->flush(); $txt = $unit->getName() . " avait une entrée en position (" . $r->getPosition()->getName() . ") couvrant la datebreak (UTC). Fermé automatiquement à " . $stop->format("Y-m-d H:i:s") . "z."; $messages .= "<p>" . $txt . "≪/p>" ; //Ouvrir une nouvelle entrée $newStartTime = $stop->modify('+1 seconde'); $entrée = nouvelle entrée(); $entry->setStart( $newStartTime ); $entry->setOperator( $r->getOperator() ); $entry->setPosition( $r->getPosition() ); $entry->setStudent( $r->getStudent() ); $em->persist($entry); // Affirmer qu'il n'y a aucune entrée future avant d'ouvrir automatiquement une nouvelle entrée $futureE = $this->checkFutureEntries($r->getPosition(),true); $openE = $this->checkOpenEntries($r->getPosition(), true); si ($futureE !== 0 || $openE !== 0) { $txt = "J'ai essayé d'ouvrir une nouvelle entrée pour " . $r->getOperator()->getSignature() . " dans la même position (" . $r->getPosition()->getName() . ") le lendemain mais il y a des entrées contradictoires."; $messages .= "<p>" . $txt . "≪/p>" ; }autre { $em->flush(); //stocker dans la base de données $txt = "Une nouvelle entrée a été ouverte pour " . $r->getOperator()->getSignature() . getName() . $messages .= "<p>" . } } } } renvoyer $messages ; }</pré> <p>J'ai même utilisé checkOpenEntries() ici pour exécuter une vérification supplémentaire pour voir s'il y a des entrées avec stoptime == NULL à cet endroit à ce moment-là. Au départ, je pensais que c'était redondant car je pensais que si une requête était en cours d'exécution et fonctionnait sur la base de données, une autre requête ne démarrerait pas tant que la première requête ne serait pas terminée. </p> <pre class="brush:php;toolbar:false;">fonction privée checkOpenEntries($position,$checkRelatives = false) { $positionsToCheck = tableau(); si ($checkRelatives == vrai) { $positionsToCheck = $position->getRelatedPositions(); $positionsToCheck[] = $position; } autre { $positionsToCheck = tableau($position); } //Obtenir toutes les entrées ouvertes pour le poste $repository = $this->em->getRepository('AppEntityPoslogEntry'); $query = $repository->createQueryBuilder('e') ->where('e.stop est NULL et e.position IN (:positions)') ->setParameter('positions', $positionsToCheck) ->getQuery(); $results = $query->getResult(); si(!isset($results[0])) { return 0 ; // indique à l'appelant qu'il n'y a aucune entrée ouverte. } autre { if (count($results) === 1) { return $results[0] ; //si exactement une entrée est ouverte, renvoie cet objet à l'appelant } autre { $body = 'Plus d'une entrée de journal ouverte pour la position ' . $position->getName() ' dans ' ' $position->getACRGroup()->getName() ' cela ne devrait pas être possible. il semble y avoir des données corrompues dans la base de données.'; $this->email($body); $output['succès'] = faux ; $output['message'] = $body ' Un e-mail automatique a été envoyé à ' $this->globalParameters->get('poslog-email-to') ' pour notifier le problème, inspection manuelle. est requis.'; $output['logdata'] = null ; return $this->prepareResponse($output); } } }</pré> <p>Dois-je utiliser une sorte de méthode de « verrouillage de la base de données » pour activer cette fonctionnalité et réaliser ce que je veux faire ? </p> <p>J'ai testé toutes les fonctionnalités et lorsque je simule différents états (saisie de NULL pour les heures d'arrêt, etc., même lorsque cela ne devrait pas l'être), tout fonctionne bien. La plupart du temps, tout fonctionne bien, mais un jour au milieu du mois, cela arrive... </p>
P粉037215587
P粉037215587

répondre à tous(1)
P粉921165181

Vous ne pouvez jamais garantir la commande (ou l'accès exclusif implicite). Essayez-le et vous vous creuserez de plus en plus profondément.

Comme Matt et KIKO l'ont mentionné dans les commentaires, vous pouvez utiliser des contraintes et des transactions, celles-ci devraient beaucoup aider car votre base de données restera propre, mais rappelez-vous que votre application doit être capable de capturer ce que la couche de base de données produit des erreurs. Ça vaut vraiment le coup d’essayer en premier.

Une autre façon de résoudre ce problème consiste à forcer le verrouillage au niveau de la base de données/de l'application.

Le verrouillage au niveau de la base de données est beaucoup plus grossier et très inexcusable si vous oubliez de déverrouiller le verrou quelque part (dans un script long).

Documentation MySQL :

Verrouiller toute la table est généralement une mauvaise idée, mais c'est possible. Cela dépend beaucoup de l'application.

Certains ORM prennent en charge la gestion des versions d'objets dès le départ et lèveront une exception si la version change pendant l'exécution. En théorie, votre application rencontrerait une exception et lorsque vous réessayeriez, vous constateriez que quelqu'un d'autre avait déjà rempli le champ et qu'elle n'était plus candidate à la mise à jour.

Application Level Locking est plus granulaire, mais tous les points du code doivent respecter le verrouillage, sinon vous revenez à la case n°1. Si votre application est distribuée (comme K8S, ou simplement déployée sur plusieurs serveurs), alors votre mécanisme de verrouillage doit également être distribué (pas d'instance locale)

Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal