Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Integralność danych przy jednoczesnym dostępie
Forum PHP.pl > Forum > Bazy danych
Kikert
Cześć,
stworzyłem stronę z konkursami online, jest oparta o chat, w którym użytkownicy wpisują odpowiedzi. Jeżeli trafią jedną z trzech opcji, użytkownik wygrywa. W takiej sytuacji do tabeli z wiadomościami była dodawania nowa, a później wykonywał się UPDATE w tabeli z konkursami, żeby ustawić zwycięzcę, jeżeli odpowiedź jest prawidłowa. Niestety, ostatnio są problemy - skrypt dwa razy dodał prawidłową odpowiedź do chatu, ale nie ustawił zwycięzcy i wygrała osoba, która podała odpowiedź jako trzecia. Niestety kto inny zajmuje się administracją - nie mogę założyć dodatkowych logów itp.

Zastanawiam się, jak zabezpieczyć skrypt przed taką sytuacją. Korzystam z mysqli, obydwie tabele na silniku InnoDB. Uwarunkowałem dodanie wiadomości do chatu od tego, czy wykonano UPDATE, ale obawiam się, że to za mało. Poniżej fragment pliku odbierającego ajaxowe zapytania:
  1. $update = $db->query(
  2. "UPDATE {$DBP_}contests SET `winner`='{$_SESSION['id']}' ".
  3. "WHERE `id`='{$contest['id']}' AND winner IS NULL AND '{$message_lowercase}' IN (answer, answer2, answer3)"
  4. );
  5.  
  6. if ($update) {
  7.  
  8. if ($db->affected_rows === 1)
  9. $json['won'] = array(
  10. 'answers' => array($message),
  11. 'winner_login' => $_SESSION['username'],
  12. 'winner_gravatar' => $_SESSION['gravatar']
  13. );
  14.  
  15. // ADD MESSAGE
  16. $db->query("INSERT INTO {$DBP_}chat SET `user_id`='{$_SESSION['id']}', `date`=NOW(), `text`='{$message}'");
  17.  
  18. }

Czy ktoś mógłby podać jakąś wskazówkę co jeszcze można zrobić?
Crozin
1. Wszelkie dane modyfikujące stan bazy danych powinny być objęte jawną transakcją.
2. W tej chwili IF sprawdzający czy zmodyfikowano dane odnosi się wyłącznie do $json['won'] = ... - zapytanie INSERT wykona się właściwie zawsze.
Kikert
Tak, wiadomość ma się wysyłać za każdym razem, bo to chat wink.gif Jednak jeżeli ktoś trafi z odpowiedzią, to przy okazji baza danych zapisuje informację, że ten user jest zwycięzcą.

Wcześniej wyglądało to w taki sposób (wyciąłem treści zapytań, są takie same):
  1. // INSERT INTO chat...
  2. // UPDATE contests...
  3. if ($update && $db->affected_rows === 1)
  4. $json['won'] = array(...);

Problem był, taki, że jeżeli wiadomość była odpowiedzią, to dodawała się do tabeli z wiadomościamu z chatu, ale nie aktualizowała kolumny contests.winner. Efekt był taki, że podano poprawną odpowiedź, która nie wygrywała konkursu.

Nie znam się za bardzo na transakcjach, ale z tego, co mi wiadomo, transakcja korzysta z tabeli w takim stanie, w jakim była w momencie rozpoczęcia transakcji. Czyli w sytuacji kiedy w minimalnych odstępach czasu przychodzą dwa wpisy z wygrywającą odpowiedzią, to wygra ten późniejszy, bo w momencie rozpoczęcia transakcji konkurs nie był jeszcze wygrany.
Crozin
  1. LOCK TABLE tbl_contests READ;
  2. START TRANSACTION;
  3. INSERT INTO tbl_chat ...;
  4. UPDATE tbl_contents ...;
  5.  
  6. -- PHP if affectedRows
  7.  
  8. COMMIT;
  9. UNLOCK TABLES;
Kikert
Wielkie dzięki, zaraz zobaczę, jak to śmiga smile.gif

Jeszcze drobne pytanie - co stanie się w sytuacji, kiedy ktoś inny też będzie wykonywał kod w tym samym czasie? Czy jego żądanie zostanie po prostu zignorowane, czy wykonane z opóźnieniem?
Crozin
W przypadku gdy na tabelę jest założona blokada inne zapytania będą musiał poczekać ze swoim wykonaniem do czasu zdjęcia tej blokady, czyli wykonają się z opóźnieniem.

Swoją drogą, jeżeli w mechanizmie sprawdzania czy dany wpis jest zwycięski korzystasz z zapytania UPDATE oraz affectedRows możesz nawet zrezygnować z blokady odczytu tabeli.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.