Когда веб-сервер получает запрос на мой сценарий PHP, я предполагаю, что сервер создает выделенный процесс для запуска сценария. Если до завершения работы скрипта поступает другой запрос к тому же скрипту, запускается другой процесс — я прав, или второй запрос будет поставлен в очередь на сервере, ожидая завершения первого запроса? (Вопрос 1)
Если первое верно, то есть один и тот же сценарий может выполняться одновременно в другом процессе, тогда они попытаются получить доступ к моей базе данных.
Когда я подключаюсь к базе данных в сценарии:
$DB = mysqli_connect("localhost", ...);
запрашивать его, проводить более или менее длительные вычисления и обновлять его, я не хочу, чтобы содержимое базы данных изменялось другим экземпляром работающего скрипта.
Вопрос 2: означает ли это, что с момента подключения к базе данных до ее закрытия:
mysqli_close($DB);
база данных заблокирована для любого доступа от других компонентов программного обеспечения? Если это так, это эффективно предотвращает одновременное выполнение экземпляров сценария.
ОБНОВИТЬ: @OllieJones любезно объяснил, что база данных не была заблокирована.
Давайте рассмотрим следующий сценарий. Сценарий в первом процессе находит подходящего пользователя в таблице Users и начинает подготовку данных для добавления этого пользователя в таблицу Counter. В этот момент сценарий в другом процессе выгружает и удаляет пользователя из таблицы Users и связанных данных в таблице Counter; затем он выгружается первым скриптом, который записывает данные для пользователя, которого больше не существует. Эти данные становятся в отключенном состоянии, то есть недоступны.
Как предотвратить такое раздор?
На современных веб-серверах существует пул процессов (или, возможно, потоков), обрабатывающих запросы от пользователей. Одновременные запросы к одному и тому же скрипту могут запустить одновременно. Каждый обработчик запросов имеет свое собственное соединение с СУБД (на самом деле они поддерживаются в пуле, но это история для другого дня).
База данных не блокируется, когда отдельные обработчики запросов используют его, если вы не заблокируете его явно, заблокировав таблицу или выполнив запрос SELECT ... FOR UPDATE
, Для получения дополнительной информации по этой глубокой теме читайте о транзакциях.
Поэтому важно писать запросы к базе данных таким образом, чтобы они не мешали друг другу. Например, если вам нужно узнать значение автоинкрементного столбца сразу после вставки строки, вы должны использовать LAST_INSERT_ID()
или же mysqli_insert_id()
вместо того, чтобы пытаться запросить базу данных: другой пользователь, возможно, вставил другую строку в это время.
Дисциплина системных тестов для масштабируемых веб-сайтов обычно включает в себя строгий нагрузочный тест, чтобы избавиться от всего этого параллелизма.
Если вы выполняете кучу работы с конкретным объектом, в вашем случае с пользователем, вы используете транзакцию.
Сначала вы делаете
BEGIN
начать транзакцию. Тогда вы делаете
SELECT whatever FROM User WHERE user_id = <<whatever>> FOR UPDATE
выбрать пользователя и пометить строку этого пользователя как занят-обновляющийся. Затем вы выполняете всю работу, необходимую для заполнения различных строк в различных таблицах, относящихся к этому пользователю.
Наконец вы делаете
COMMIT
Если вы все испортили или не хотите пережить изменения, вы делаете
ROLLBACK
и все ваши изменения будут восстановлены в своем состоянии прямо перед SELECT ... FOR UPDATE
,
Почему это работает? Потому что, если другой клиент делает то же самое SELECT .... FOR UPDATE
MySQL задержит этот запрос до тех пор, пока не получит первый COMMIT
или же ROLLBACK
,
Если другой клиент работает с другим идентификатором пользователя, операции могут выполняться одновременно.
Вам нужен метод доступа InnoDB для использования транзакций: MyISAM не поддерживает их.
Многократное чтение может быть выполнено одновременно, если есть операция записи, тогда она заблокирует все другие операции. Чтение заблокирует все записи.