Bagaimanakah PHP dan MySQL mengendalikan permintaan serentak?
P粉037215587
P粉037215587 2023-09-05 12:17:19
0
1
562
<p>Saya mesti kehilangan sesuatu tentang cara PHP/Symfony mengendalikan permintaan serentak, atau mungkin cara mengendalikan pertanyaan serentak yang berpotensi pada pangkalan data...</p> <p>Kod ini nampaknya melakukan perkara yang mustahil - ia secara rawak (kira-kira sebulan sekali) mencipta salinan entiti baharu di bahagian bawah. Kesimpulan saya ialah apabila dua pelanggan membuat permintaan yang sama dua kali, dan kedua-dua utas melaksanakan pertanyaan SELECT pada masa yang sama, pilih entri dengan berhenti == NULL, dan kemudian mereka berdua (?) menetapkan masa berhenti entri itu, ia mesti Apabila ini berlaku, mereka berdua menulis entri baru.</p> <p>Setahu saya, ini adalah rangka logik saya: </p> <ol> <li>Dapatkan semua penyertaan dengan NULL masa berhenti</li> <li>Gelung melalui entri</li> <li>Teruskan hanya jika tarikh input (UTC) berbeza daripada tarikh semasa (UTC)</li> <li>Tetapkan masa berhenti untuk entri terbuka kepada 23:59:59 dan siram ke pangkalan data</li> <li>Bina entri baharu bermula jam 00:00:00 keesokan harinya</li> <li>Tegaskan bahawa tiada entri terbuka lain di lokasi ini</li> <li>Tegaskan bahawa tiada entri akan datang di lokasi ini</li> <li>Hanya ini - siram entri baharu ke pangkalan data</li> </ol> <p>Pengawal dimatikan dan dihidupkan secara automatik</p> <pre class="brush:php;toolbar:false;">//jika entri menjangkau waktu subuh (tengah malam) tutupnya dan buka entri baharu pada permulaan hari berikutnya fungsi peribadi autocloseAndOpen($units) { $now = DateTime baharu("sekarang", DateTimeZone baharu("UTC")); $repository = $this->em->getRepository('AppEntityPoslogEntry'); $query = $repository->createQueryBuilder('e') ->di mana('e.stop is NULL') ->getQuery(); $results = $query->getResult(); if (!isset($results[0])) { return null; //tiada entri terbuka sama sekali }$em = $ini->em; $mesej = ""; foreach ($hasil sebagai $r) { if ($r->getPosition()->getACRGroup() == $unit) { //hanya sentuh entri pengguna sendiri $start = $r->getStart(); //Tegaskan entri yang merangkumi pecah tarikh $startStr = $start->format("Y-m-d"); //Diperlukan untuk perbandingan, jika $start->format("Y-m-d") diletakkan dalam klausa perbandingan PHP masih akan membandingkan objek datetime yang sedang diformat, bukan output pemformatan. $nowStr = $now->format("Y-m-d"); //Diperlukan untuk perbandingan, jika $start->format("Y-m-d") diletakkan dalam klausa perbandingan PHP masih akan membandingkan objek datetime yang sedang diformat, bukan output pemformatan. if ($startStr < $nowStr) { $stop = DateTimeImmutable baharu($start->format("Y-m-d")."23:59:59", DateTimeZone baharu("UTC")); $r->setStop($stop); $em->flush(); $txt = $unit->getName() . " mempunyai entri dalam kedudukan (" . $r->getPosition()->getName() . ") merangkumi tarikh pecah (UTC). Ditutup secara automatik pada " . $stop->format("Y-m-d H:i:s") . "z."; $mesej .= "<p>" . $txt . "</p>"; //Buka entri baharu $newStartTime = $stop->modify('+1 saat'); $entry = new Entry(); $entry->setStart( $newStartTime ); $entry->setOperator( $r->getOperator() ); $entry->setPosition( $r->getPosition() ); $entry->setStudent( $r->getStudent() ); $em->berterusan($entry); //Tegaskan bahawa tiada entri akan datang sebelum membuka automatik entri baharu $futureE = $this->checkFutureEntries($r->getPosition(),true); $openE = $this->checkOpenEntries($r->getPosition(), true); jika ($futureE !== 0 || $openE !== 0) { $txt = "Cuba membuka entri baharu untuk " . $r->getOperator()->getSignature() . " dalam kedudukan yang sama (" . $r->getPosition()->getName() . ") pada hari berikutnya tetapi terdapat entri yang bercanggah."; $mesej .= "<p>" . $txt . "</p>"; }lain { $em->flush(); //simpan ke DB $txt = "Entri baharu telah dibuka untuk " $r->getOperator()->getSignature() . getName() ")" $messages .= "<p>" } } } } kembalikan $mesej; }</pre> <p>Saya juga menggunakan checkOpenEntries() di sini untuk menjalankan semakan tambahan untuk melihat sama ada terdapat sebarang entri dengan masa henti == NULL di lokasi itu pada masa ini. Pada mulanya, saya fikir ini berlebihan kerana saya fikir jika satu permintaan berjalan dan beroperasi pada pangkalan data, permintaan lain tidak akan bermula sehingga permintaan pertama selesai. </p> <pre class="brush:php;toolbar:false;">semak fungsi peribadiOpenEntries($position,$checkRelatives = false) { $positionsToCheck = array(); if ($checkRelatives == benar) { $positionsToCheck = $position->getRelatedPositions(); $positionsToCheck[] = $position; } lain { $positionsToCheck = array($position); } //Dapatkan semua penyertaan terbuka untuk jawatan $repository = $this->em->getRepository('AppEntityPoslogEntry'); $query = $repository->createQueryBuilder('e') ->di mana('e.stop ialah NULL dan e.position IN (:positions)') ->setParameter('positions', $positionsToCheck) ->getQuery(); $results = $query->getResult(); if(!isset($results[0])) { return 0; //memberitahu pemanggil bahawa tiada entri terbuka } lain { if (count($results) === 1) { return $results[0]; //jika betul-betul satu entri terbuka, kembalikan objek itu kepada pemanggil } lain { $body = 'Mendapati lebih daripada 1 entri log terbuka untuk kedudukan ' $position->getName() ' dalam ' $position->getACRGroup()->getName() . nampaknya terdapat data yang rosak dalam pangkalan data.'; $this->email($body); $output['success'] = palsu; $output['message'] = $body . E-mel automatik telah dihantar ke ' $this->globalParameters->get('poslog-email-to') adalah diperlukan.'; $output['logdata'] = null; kembalikan $this->prepareResponse($output); } } }</pre> <p>Adakah saya perlu menggunakan beberapa jenis kaedah "pangkalan data kunci" untuk membolehkan fungsi ini mencapai apa yang saya mahu lakukan? </p> <p>Saya telah menguji semua fungsi dan apabila saya mensimulasikan pelbagai keadaan (memasukkan NULL untuk masa henti, dsb., walaupun tidak sepatutnya), semuanya berfungsi dengan baik. Selalunya, semuanya berfungsi dengan baik, tetapi satu hari pada pertengahan bulan, ini berlaku... </p>
P粉037215587
P粉037215587

membalas semua(1)
P粉921165181

Anda tidak boleh menjamin pesanan (atau akses eksklusif tersirat). Cubalah dan anda akan menggali diri anda lebih dalam dan lebih dalam.

Seperti yang dinyatakan oleh Matt dan KIKO dalam ulasan, anda boleh menggunakan kekangan dan urus niaga, ini sepatutnya banyak membantu kerana pangkalan data anda akan kekal bersih, tetapi ingat aplikasi anda perlu dapat menangkap kesilapan yang dihasilkan oleh lapisan pangkalan data. Pasti berbaloi mencuba dulu.

Cara lain untuk menangani masalah ini ialah dengan memaksa penguncian peringkat pangkalan data/aplikasi.

Penguncian tahap pangkalan data adalah jauh lebih kasar dan sangat tidak boleh dimaafkan jika anda terlupa untuk melepaskan kunci di suatu tempat (dalam skrip berjalan lama).

Dokumentasi MySQL:

Mengunci seluruh meja biasanya merupakan idea yang tidak baik, tetapi ia boleh dilakukan. Ini banyak bergantung pada aplikasi.

Sesetengah ORM menyokong versi objek di luar kotak dan akan membuang pengecualian jika versi berubah semasa pelaksanaan. Secara teorinya, aplikasi anda akan mendapat pengecualian dan apabila anda mencuba lagi, anda akan mendapati bahawa orang lain telah mengisi medan tersebut dan ia bukan lagi calon untuk kemas kini.

Penguncian Tahap Aplikasi lebih berbutir, tetapi semua titik dalam kod perlu menghormati penguncian, jika tidak, anda kembali ke petak #1. Jika aplikasi anda diedarkan (seperti K8S, atau hanya digunakan pada berbilang pelayan), maka mekanisme penguncian anda juga mesti diedarkan (bukan contoh tempatan)

Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan