MySQL
SELECT ... FOR UPDATE com UPDATE
Usando transações com InnoDB (auto-commit desativado), um
SELECT ... FOR UPDATE
permite que uma sessão bloqueie temporariamente um determinado registro (ou registros) para que nenhuma outra sessão possa atualizá-lo. Então, dentro da mesma transação, a sessão pode realmente executar um UPDATE
no mesmo registro e confirmar ou reverter a transação. Isso permitiria que você bloqueasse o registro para que nenhuma outra sessão pudesse atualizá-lo enquanto talvez você fizesse alguma outra lógica de negócios. Isso é feito com travamento. O InnoDB utiliza índices para bloquear registros, portanto, bloquear um registro existente parece fácil -- basta bloquear o índice para esse registro.
SELECIONE ... PARA ATUALIZAR com INSERT
No entanto, para usar
SELECT ... FOR UPDATE
com INSERT
, como você bloqueia um índice para um registro que ainda não existe? Se você estiver usando o nível de isolamento padrão de REPEATABLE READ
, o InnoDB também utilizará gap fechaduras. Contanto que você saiba o id
(ou mesmo intervalo de ids) para bloquear, então o InnoDB pode bloquear a lacuna para que nenhum outro registro possa ser inserido nessa lacuna até terminarmos com isso. Se o seu
id
coluna fosse uma coluna de incremento automático, então SELECT ... FOR UPDATE
com INSERT INTO
seria problemático porque você não saberia qual é o novo id
era até você inseri-lo. No entanto, como você conhece o id
que você deseja inserir, SELECT ... FOR UPDATE
com INSERT
vai funcionar. AVISO
No nível de isolamento padrão,
SELECT ... FOR UPDATE
em um registro inexistente não bloquear outras transações. Então, se duas transações fizerem um SELECT ... FOR UPDATE
no mesmo registro de índice inexistente, ambos obterão o bloqueio e nenhuma transação poderá atualizar o registro. Na verdade, se eles tentarem, um deadlock será detectado. Portanto, se você não quiser lidar com um impasse, faça o seguinte:
INSERIR EM...
Inicie uma transação e execute o
INSERT
. Faça sua lógica de negócios e confirme ou reverta a transação. Assim que você fizer o INSERT
no índice de registro inexistente na primeira transação, todas as outras transações serão bloqueadas se tentarem INSERT
um registro com o mesmo índice exclusivo. Se a segunda transação tentar inserir um registro com o mesmo índice após a primeira transação confirmar a inserção, ela receberá um erro de "chave duplicada". Trate de acordo. SELECIONAR ... BLOQUEAR NO MODO DE COMPARTILHAMENTO
Se você selecionar com
LOCK IN SHARE MODE
antes do INSERT
, se uma transação anterior inseriu esse registro, mas ainda não foi confirmada, o SELECT ... LOCK IN SHARE MODE
bloqueará até que a transação anterior seja concluída. Portanto, para reduzir a chance de erros de chave duplicada, especialmente se você mantiver os bloqueios por algum tempo enquanto executa a lógica de negócios antes de confirmá-los ou revertê-los:
SELECT bar FROM FooBar WHERE foo = ? LOCK FOR UPDATE
- Se nenhum registro for retornado, então
INSERT INTO FooBar (foo, bar) VALUES (?, ?)