admin21.03.2015 19:01

Выборка через запрос SELECT … FOR UPDATE

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

Для того, чтобы избежать вышеупомянутой ситуации необходимо выполнить не просто SELECT запрос, а его расширенную версию, о которой многие и не подозревают: SELECT … FOR UPDATE.

Таким образом, при выполнении данного запроса, все затронутые записи в базе данных будут заблокированы до завершения сеанса работы с БД или до момента обновления данных записей. Другой скрипт не сможет выбрать заблокированные записи до тех пор, пока не наступит одно из упомянутых условий.

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

Таким образом, если собрать всё воедино, то получится скрипт со следующим содержанием:

mysql_query("SET autocommit = 0");
$result = mysql_query("SELECT * FROM table WHERE locked = 0 LIMIT 1 FOR UPDATE");

$row = mysql_fetch_assoc($result);

mysql_query("UPDATE table SET locked = 1 WHERE id = 1;");
mysql_query("COMMIT;");

Представьте что у нас есть 2 записи в таблице. Мы запускаем два параллельных скрипта с данным содержанием. В результате, первый процесс, который сможет достучаться до базы сделает выборку записи и сразу же заменит значение в поле locked на 1. В результате второй скрипт проигнорирует первую запись. В данном случае, даже если скрипты достучались бы до БД одновременно, второй скрипт всё равно не смог бы выбрать первую запись, т.к. при выборке первым процессом она будет заблокирована.