В КУРСЕ?

Разбираемся в теме

Два меча за цену одного: где PHP-игра теряет целостность данных

В браузерной ролевой игре кнопка покупки выглядит простой: списать монеты и добавить предмет. Но два запроса могут выполняться одновременно, а ответ сервера иногда теряется по дороге. Если учесть только обычный последовательный сценарий, игрок получит неверный баланс или лишний предмет. Разбирать проблему удобнее на небольшом вымышленном примере без настоящих платежей.

Один баланс могут прочитать два запроса

У персонажа сто игровых монет, меч стоит семьдесят. Два параллельных запроса независимо читают сто, каждый считает средств достаточно и вычисляет остаток тридцать. Если оба затем записывают это заранее вычисленное значение и добавляют по мечу, в инвентаре оказываются два предмета при остатке тридцать. Обычная проверка баланса была выполнена дважды, но не защитила от совместного выполнения. Важно проверять не только правильность отдельного обработчика, но и возможное чередование его действий с другим запросом.

Связанные изменения образуют одну операцию

Списание монет и добавление предмета должны либо завершиться вместе, либо вместе отмениться. Для таблиц InnoDB в MySQL это организуют транзакцией. В PHP через PDO её начинают методом beginTransaction, подтверждают commit и при неуспехе откатывают rollBack. Однако одна транзакция не исправляет любой способ чтения автоматически. В рассматриваемом варианте баланс конкретного персонажа читают внутри транзакции с блокировкой FOR UPDATE, затем проверяют средства, меняют баланс и инвентарь. Конкурирующий обработчик, использующий тот же порядок и строку блокировки, продолжит после освобождения блокировки и проверит уже актуальное значение.

Повтор запроса отличается от второй покупки

После успешной покупки сервер может не успеть доставить ответ клиенту. Повторная отправка тогда не обязательно означает желание купить ещё один предмет. Для различения используют идентификатор операции и сохранённый результат, связанные с игроком. Уникальность такого идентификатора обеспечивают в базе, а запись операции согласуют с изменениями баланса и инвентаря в одной транзакции. При повторе уже завершённого действия возвращают прежний результат. Это отдельная задача от защиты баланса: даже при достаточном запасе монет случайный повтор не должен превращаться в новую покупку.

Проверки остаются на сервере

Стоимость предмета и право управлять персонажем сервер определяет по доверенным данным, а не принимает готовый баланс из браузера. Подготовленные запросы PDO помогают передавать значения отдельно от SQL, но не заменяют проверку владельца персонажа, игровых условий и допустимости действия. Ошибки базы, включая взаимные блокировки, требуют предусмотренной обработки; нельзя сообщать об успехе до подтверждённого завершения операции. Рассмотренный механизм защищает конкретный переход состояния, а полная игра дополнительно требует аутентификации, защиты запросов и других мер безопасности.

Попробуйте на практике

На бумаге сравните ошибочную и согласованную покупку меча за семьдесят монет при исходном балансе сто. Рабочую базу данных для задания использовать не нужно.

  1. Распишите два запроса по этапам: чтение, проверка, изменение баланса, добавление предмета. Расположите оба чтения до первой записи.
  2. Посчитайте результат, если оба обработчика сохраняют рассчитанный заранее остаток тридцать и независимо добавляют предмет.
  3. Повторите схему с блокировкой одной строки персонажа внутри транзакции. Второй обработчик читает актуальный баланс после завершения первого.
  4. Добавьте потерю ответа после первой успешной покупки и повтор того же идентификатора операции. Укажите, какой сохранённый результат следует вернуть.

Как проверить результат. В ошибочном варианте два меча сочетаются с остатком тридцать. При согласованной проверке вторая отдельная покупка отклоняется из-за недостатка монет. Повтор уже выполненной операции возвращает её результат без нового списания и предмета.

Частые вопросы

Достаточно ли отключить кнопку после нажатия?

Это улучшает поведение интерфейса, но не предотвращает параллельные запросы, сетевые повторы и обращения в обход страницы. Согласованность должна обеспечиваться сервером и базой.

Подготовленный SQL-запрос автоматически защищает от двойной покупки?

Нет. Подготовка запроса решает другую задачу. Для покупки нужны согласованные изменения состояния, управление конкурирующими действиями и отдельная обработка повторов операции.

Самостоятельный разбор темы. Содержание конкретной обучающей программы здесь не представлено.

Зарегистрируйтесь, чтобы уточнить возможность доступа к этому материалу

Зарегистрироваться
← К списку материалов