A condição where será respeitada durante uma situação de corrida, mas você deve ter cuidado ao verificar quem ganhou a corrida.
Considere a seguinte demonstração de como isso funciona e por que você deve ter cuidado.
Primeiro, configure algumas tabelas mínimas.
CREATE TABLE table1 (
`id` TINYINT UNSIGNED NOT NULL PRIMARY KEY,
`locked` TINYINT UNSIGNED NOT NULL,
`updated_by_connection_id` TINYINT UNSIGNED DEFAULT NULL
) ENGINE = InnoDB;
CREATE TABLE table2 (
`id` TINYINT UNSIGNED NOT NULL PRIMARY KEY
) ENGINE = InnoDB;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);
id
desempenha o papel de id
em sua tabela, updated_by_connection_id
age como assignedPhone
, e locked
como reservationCompleted
. Agora vamos começar o teste de corrida. Você deve ter 2 janelas de linha de comando/terminal abertas, conectadas ao mysql e usando o banco de dados onde você criou essas tabelas.
Conexão 1
start transaction;
Conexão 2
start transaction;
Conexão 1
UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
Conexão 2
UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
A conexão 2 está agora esperando
Conexão 1
SELECT * FROM table1 WHERE id = 1;
commit;
Neste ponto, a conexão 2 é liberada para continuar e gera o seguinte:
Conexão 2
SELECT * FROM table1 WHERE id = 1;
commit;
Tudo parece bem. Vemos que sim, a cláusula WHERE foi respeitada em uma situação de corrida.
A razão pela qual eu disse que você tinha que ter cuidado, porém, é porque em uma aplicação real as coisas nem sempre são tão simples. Você PODE ter outras ações acontecendo dentro da transação, e isso pode realmente alterar os resultados.
Vamos redefinir o banco de dados com o seguinte:
delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);
E agora, considere esta situação, onde um SELECT é executado antes do UPDATE.
Conexão 1
start transaction;
SELECT * FROM table2;
Conexão 2
start transaction;
SELECT * FROM table2;
Conexão 1
UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
Conexão 2
UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
A conexão 2 está agora esperando
Conexão 1
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
Neste ponto, a conexão 2 é liberada para continuar e gera o seguinte:
Bom, vamos ver quem ganhou:
Conexão 2
SELECT * FROM table1 WHERE id = 1;
Espere o que? Por que
locked
0 e updated_by_connection_id
NULO?? Este é o cuidado que mencionei. O culpado é, na verdade, o fato de termos feito um select no início. Para obter o resultado correto, podemos executar o seguinte:
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
Usando SELECT ... FOR UPDATE podemos obter o resultado correto. Isso pode ser muito confuso (como foi para mim, originalmente), pois um SELECT e um SELECT ... FOR UPDATE estão dando dois resultados diferentes.
A razão pela qual isso acontece é por causa do nível de isolamento padrão
READ-REPEATABLE
. Quando o primeiro SELECT é feito, logo após o start transaction;
, um instantâneo é criado. Todas as leituras futuras sem atualização serão feitas a partir desse instantâneo. Portanto, se você simplesmente SELECT ingenuamente depois de fazer a atualização, ele extrairá as informações desse instantâneo original, que é antes a linha foi atualizada. Ao fazer um SELECT ... FOR UPDATE você o força a obter as informações corretas.
No entanto, novamente, em uma aplicação real, isso pode ser um problema. Digamos, por exemplo, que sua solicitação seja encapsulada em uma transação e, após realizar a atualização, você deseja gerar algumas informações. A coleta e a saída dessas informações podem ser tratadas por código separado e reutilizável, que você NÃO deseja desordenar com cláusulas FOR UPDATE "por via das dúvidas". Isso levaria a muita frustração devido ao bloqueio desnecessário.
Em vez disso, você vai querer fazer uma trilha diferente. Você tem muitas opções aqui.
Um, é certificar-se de que você confirma a transação após a conclusão do UPDATE. Na maioria dos casos, esta é provavelmente a escolha melhor e mais simples.
Outra opção é não tentar usar SELECT para determinar o resultado. Em vez disso, você pode ler as linhas afetadas e usá-las (1 linha atualizada versus 0 linha atualizada) para determinar se o UPDATE foi bem-sucedido.
Outra opção, e que uso com frequência, pois gosto de manter uma única solicitação (como uma solicitação HTTP) totalmente envolvida em uma única transação, é garantir que a primeira instrução executada em uma transação seja a UPDATE ou um SELECT ... FOR UPDATE . Isso fará com que o instantâneo NÃO seja tirado até que a conexão tenha permissão para continuar.
Vamos redefinir nosso banco de dados de teste novamente e ver como isso funciona.
delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);
Conexão 1
start transaction;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
Conexão 2
start transaction;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
A conexão 2 está agora esperando.
Conexão 1
UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
A conexão 2 agora está liberada.
Conexão 2
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
| 1 | 1 | 1 |
+----+--------+--------------------------+
Aqui você pode realmente fazer com que seu código do lado do servidor verifique os resultados deste SELECT e saiba que é preciso, e nem mesmo continue com as próximas etapas. Mas, para completar, vou terminar como antes.
UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
Agora você pode ver que na Conexão 2 o SELECT e SELECT ... FOR UPDATE dão o mesmo resultado. Isso ocorre porque o instantâneo do qual o SELECT lê não foi criado até que a Conexão 1 tenha sido confirmada.
Então, de volta à sua pergunta original:Sim, a cláusula WHERE é verificada pela instrução UPDATE, em todos os casos. No entanto, você deve ter cuidado com quaisquer SELECTs que possa estar fazendo, para evitar determinar incorretamente o resultado dessa UPDATE.
(Sim, outra opção é alterar o nível de isolamento da transação. No entanto, eu realmente não tenho experiência com isso e quaisquer pegadinhas que possam existir, então não vou entrar nisso.)