O que você quer é SELECT ... FOR UPDATE no contexto de uma transação. SELECT FOR UPDATE coloca um bloqueio exclusivo nas linhas selecionadas, como se você estivesse executando UPDATE. Também é executado implicitamente no nível de isolamento READ COMMITTED, independentemente do nível em que o nível de isolamento está definido explicitamente. Lembre-se de que SELECT ... FOR UPDATE é muito ruim para simultaneidade e só deve ser usado quando for absolutamente necessário. Também tem uma tendência a se multiplicar em uma base de código à medida que as pessoas cortam e colam.
Aqui está uma sessão de exemplo do banco de dados Sakila que demonstra alguns dos comportamentos das consultas FOR UPDATE.
Primeiro, só para esclarecer, defina o nível de isolamento da transação como REPEATABLE READ. Isso normalmente é desnecessário, pois é o nível de isolamento padrão para o InnoDB:
session1> SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
session1> BEGIN;
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | WILLIAMS |
+------------+-----------+
1 row in set (0.00 sec)
Na outra sessão, atualize esta linha. Linda se casou e mudou seu nome:
session2> UPDATE customer SET last_name = 'BROWN' WHERE customer_id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
De volta à sessão1, por estarmos em REPEATABLE READ, Linda ainda é LINDA WILLIAMS:
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | WILLIAMS |
+------------+-----------+
1 row in set (0.00 sec)
Mas agora, queremos acesso exclusivo a essa linha, por isso chamamos FOR UPDATE na linha. Observe que agora recuperamos a versão mais recente da linha, que foi atualizada na sessão2 fora desta transação. Isso não é REPETÍVEL, LEIA COMPROMISSO
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3 FOR UPDATE;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | BROWN |
+------------+-----------+
1 row in set (0.00 sec)
Vamos testar o bloqueio definido na sessão1. Observe que a sessão2 não pode atualizar a linha.
session2> UPDATE customer SET last_name = 'SMITH' WHERE customer_id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
Mas ainda podemos selecionar
session2> SELECT c.customer_id, c.first_name, c.last_name, a.address_id, a.address FROM customer c JOIN address a USING (address_id) WHERE c.customer_id = 3;
+-------------+------------+-----------+------------+-------------------+
| customer_id | first_name | last_name | address_id | address |
+-------------+------------+-----------+------------+-------------------+
| 3 | LINDA | BROWN | 7 | 692 Joliet Street |
+-------------+------------+-----------+------------+-------------------+
1 row in set (0.00 sec)
E ainda podemos atualizar uma tabela filho com um relacionamento de chave estrangeira
session2> UPDATE address SET address = '5 Main Street' WHERE address_id = 7;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1 Changed: 1 Warnings: 0
session1> COMMIT;
Outro efeito colateral é que você aumenta sua probabilidade de causar um impasse.
No seu caso específico, você provavelmente deseja:
BEGIN;
SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR UPDATE;
-- do some other stuff
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>;
COMMIT;
Se a parte "faça outras coisas" for desnecessária e você realmente não precisar manter as informações sobre a linha, o SELECT FOR UPDATE é desnecessário e desperdício, e você poderá executar uma atualização:
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;
Espero que isso faça algum sentido.
items
WHEREstatus
= 'pendente' LIMIT 1 FOR UPDATE;" e ambos vêem a mesma linha, um trava o outro. Eu estava esperando que de alguma forma ele seria capaz de by-pass a linha bloqueada e vá para o próximo item que estava pendente ..