Niezbędne pliki cookies pozwalają stronie działać. Za Twoją zgodą użyję też cookies analitycznych (statystyki odwiedzin) i treści zewnętrznych (kalendarz rezerwacji). Decyzję zmienisz w każdej chwili w stopce strony.

Polityka cookies · Polityka prywatności

Limit miejsc, którego nie da się przekroczyć

KMP-002 Snippet 1 min czytania

Dwóch rodziców klika „Zapisz” w tej samej sekundzie, a zostało jedno miejsce. Jak zablokować overbooking w Laravelu transakcją i blokadą wiersza.

Najczęstszy błąd w systemach zapisów: sprawdzenie liczby wolnych miejsc i zapis rezerwacji to dwa osobne zapytania. Między nimi może wejść drugi request. Przy otwarciu zapisów na popularny turnus to nie teoria, tylko pewność.

Rozwiązanie: transakcja i lockForUpdate

<?php

use App\Exceptions\CampSessionFullException;
use App\Models\Booking;
use App\Models\CampSession;
use Illuminate\Support\Facades\DB;

function reserve(int $sessionId, array $data): Booking
{
    return DB::transaction(function () use ($sessionId, $data) {
        // Blokujemy wiersz turnusu do końca transakcji.
        // Drugi request czeka tutaj, zamiast liczyć te same miejsca.
        $session = CampSession::whereKey($sessionId)
            ->lockForUpdate()
            ->firstOrFail();

        $taken = $session->bookings()
            ->whereIn('status', ['pending', 'paid'])
            ->count();

        if ($taken >= $session->capacity) {
            throw new CampSessionFullException($session);
        }

        return $session->bookings()->create($data + ['status' => 'pending']);
    }, attempts: 3); // ponów przy deadlocku
}

Dlaczego to działa

SELECT … FOR UPDATE w InnoDB zakłada blokadę na wierszu turnusu. Drugi request, który chce zablokować ten sam wiersz, czeka, aż pierwsza transakcja się zakończy, i dopiero wtedy liczy miejsca, już z nową rezerwacją.

Co jeszcze

  • Rezerwacje pending też zajmują miejsce. Nieopłacone anuluje osobny job po upływie terminu płatności, który zwalnia miejsce.
  • Komunikat dla rodzica nie może brzmieć „Błąd 500”. Wyjątek CampSessionFullException mapujemy na czytelną informację i propozycję listy rezerwowej.
  • Limit sprawdzany w JavaScripcie to tylko podpowiedź dla użytkownika. Prawdę mówi serwer.