O bloqueio de tabelas evita que outros usuários do banco de dados afetem as linhas / tabelas que você bloqueou. Mas as travas, por si mesmas, NÃO garantirão que sua lógica saia em um estado consistente.
Pense em um sistema bancário. Quando você paga uma conta online, há pelo menos duas contas afetadas pela transação: Sua conta, da qual o dinheiro é retirado. E a conta do receptor, para a qual o dinheiro é transferido. E a conta do banco, na qual depositarão de bom grado todas as taxas de serviço cobradas na transação. Dado (como todos sabem atualmente) que os bancos são extraordinariamente estúpidos, digamos que seu sistema funcione assim:
$balance = "GET BALANCE FROM your ACCOUNT";
if ($balance < $amount_being_paid) {
charge_huge_overdraft_fees();
}
$balance = $balance - $amount_being paid;
UPDATE your ACCOUNT SET BALANCE = $balance;
$balance = "GET BALANCE FROM receiver ACCOUNT"
charge_insane_transaction_fee();
$balance = $balance + $amount_being_paid
UPDATE receiver ACCOUNT SET BALANCE = $balance
Agora, sem bloqueios e sem transações, este sistema está vulnerável a várias condições de corrida, a maior das quais são vários pagamentos realizados em sua conta ou na conta do receptor em paralelo. Embora seu código tenha seu saldo recuperado e esteja executando o grande_overdraft_fees () e outros enfeites, é inteiramente possível que algum outro pagamento execute o mesmo tipo de código em paralelo. Eles recuperarão seu saldo (digamos, $ 100), farão suas transações (retire os $ 20 que você está pagando e os $ 30 com os quais estão enganando você) e agora os dois caminhos de código têm dois saldos diferentes: $ 80 e $ 70. Dependendo de qual terminar por último, você acabará com um desses dois saldos em sua conta, em vez dos $ 50 que deveria ter recebido ($ 100 - $ 20 - $ 30). Neste caso, "erro bancário a seu favor"
Agora, digamos que você use bloqueios. O pagamento da sua conta ($ 20) chega primeiro, então ganha e bloqueia o registro da sua conta. Agora você tem uso exclusivo, podendo deduzir os $ 20 do saldo, e reaver o novo saldo de volta em paz ... e sua conta fica com $ 80 como esperado. Mas ... uhoh ... Você tenta atualizar a conta do receptor, e ela está bloqueada, e bloqueada por mais tempo do que o código permite, expirando sua transação ... Estamos lidando com bancos estúpidos, então, em vez de ter o erro adequado manipulando, o código apenas puxa um exit()
, e seus $ 20 desaparecem em uma nuvem de elétrons. Agora você está sem $ 20 e ainda deve $ 20 ao receptor, e seu telefone é retomado.
Então ... insira as transações. Você inicia uma transação, debita $ 20 de sua conta, tenta creditar o receptor com $ 20 ... e algo explode novamente. Mas desta vez, em vez de exit()
, o código pode apenas fazer rollback
, e puf, seus $ 20 são magicamente adicionados de volta à sua conta.
No final, tudo se resume a isso:
Os bloqueios impedem que qualquer outra pessoa interfira nos registros do banco de dados com os quais você está lidando. As transações evitam que quaisquer erros "posteriores" interfiram nas coisas "anteriores" que você fez. Nenhum dos dois pode garantir que tudo dê certo no final. Mas juntos, eles fazem.
na lição de amanhã: The Joy of Deadlocks.
Você quer um
SELECT ... FOR UPDATE
ouSELECT ... LOCK IN SHARE MODE
dentro de uma transação, como você disse, já que normalmente SELECTs, não importa se eles estão em uma transação ou não, não irão bloquear uma tabela. Qual você escolher depende se você deseja que outras transações possam ler essa linha enquanto sua transação está em andamento.http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html
START TRANSACTION WITH CONSISTENT SNAPSHOT
não resolverá o problema para você, pois outras transações ainda podem surgir e modificar essa linha. Isso é mencionado bem no topo do link abaixo.http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html
fonte
Os conceitos de transação e bloqueios são diferentes. No entanto, a transação usava bloqueios para ajudá-la a seguir os princípios do ACID. Se você deseja que a tabela evite que outros leiam / escrevam no mesmo ponto de tempo enquanto você está lendo / gravando, você precisa de um bloqueio para fazer isso. Se você deseja garantir a integridade e consistência dos dados, é melhor usar as transações. Acho que conceitos mistos de níveis de isolamento em transações com bloqueios. Por favor, procure níveis de isolamento de transações, SERIALIZE deve ser o nível que você deseja.
fonte
Tive um problema semelhante ao tentar um
IF NOT EXISTS ...
e depois executar umINSERT
que causou uma condição de corrida quando vários threads estavam atualizando a mesma tabela.Eu encontrei a solução para o problema aqui: Como escrever consultas INSERT IF NOT EXISTS no SQL padrão
Sei que isso não responde diretamente à sua pergunta, mas o mesmo princípio de realizar uma verificação e inserir como uma única instrução é muito útil; você deve ser capaz de modificá-lo para realizar sua atualização.
fonte
Você está confuso com bloqueio e transação. São duas coisas diferentes no RMDB. O bloqueio evita operações simultâneas enquanto a transação se concentra no isolamento de dados. Confira este ótimo artigo para esclarecimentos e algumas soluções elegantes.
fonte
Eu usaria um
para começar, e um
para terminar.
Qualquer coisa que você fizer no meio é isolada dos outros usuários do seu banco de dados se o seu mecanismo de armazenamento suportar transações (que é InnoDB).
fonte