Pergunta

Às vezes acontece que dois administradores em nossa equipe de suporte estão tentando fazer a mesma operação confidencial na linha da tabela db (digamos, modificando o valor na linha). Precisamos prevenir isso. (O bloqueio de linha não é possível porque as tabelas são "myisam")

Já pensei em várias soluções:

definindo o valor antigo no formulário e comparando-o com o atual no envio

   <input name="money"><input type="hidden" name="old_money" value="10">

e antes de atualizar:

   $currentmoney=value_from_query("select money from mytable","money");
   if($currentmoney!=$_REQUEST["old_money"]){
      return "value changed to $currentmoney while you were editing it, are you sure you still want to change it?!??!?!?!?";
   }
   else{
     mysql_query("update everyonesmoney set money='".intval($_REQUEST["money"])."' where user='$user_id'");
     return true;
   }

mas pode haver a seguinte situação:

  1. o usuário precisa que o valor monetário seja alterado de 9 $ para 10 $

  2. admin1 muda seu dinheiro para 10 $

  3. o usuário gasta de forma inteligente 1 $, então seu dinheiro atual torna-se 9 $ novamente!

  4. admin2 muda seu dinheiro para 10 $ sem aviso prévio.

criando configuração de carimbo de data / hora (coluna updated_at) na linha

E fazendo o mesmo que na solução 1. Isso tem a vantagem de dizer mais do que uma simples comparação de dados. Podemos dizer com certeza se os dados foram alterados enquanto estávamos mexendo no formulário ou não. desvantagem - não podemos rastrear qual coluna exatamente foi alterada, a menos que a combinemos com a solução 1

   <input type="hidden" name="formtimestamp" value="<? echo time();?>">

e durante a atualização:

   $query_add = ($overriden ? "" : " and updated_at>'".securevalue($_REQUEST["formtimestamp"])."'");
   if(mysql_affected_rows(mysql_query("update everyonesmoney set money='".intval($_REQUEST["money"])."', updated_at=NOW() where user='$user_id' ".$query_add))==0){
      return "some values were changed by someone else while you were editing it, are you sure you still want to change it?!??!?!?!?";
   }
   else{
     return true;
   }

criando o arquivo temporário de comprimento 0 com nome específico de objeto / ação

Criá-lo / bloqueá-lo durante a atualização e verificar sua existência / carimbo de data antes da atualização.

Antes da atualização:

   $myfname="/tmp/user{$user_id}EDITMONEY.tmp";
   $timedifference=((time()-filectime($myfname)); //in seconds
   if(file_exists($myfname) and ($timedifference<60) and (!$overriden)){ // a minute difference
      $currentmoney=value_from_query("select money from mytable","money");
      return "money were edited by someone else $timedifference seconds ago and set to {$currentmoney}, are you sure you still want to change it?!??!?!?!?";
   }else{
      $fp = fopen("/tmp/user".intval($_REQUEST["user_id"])."EDITMONEY.tmp", "r+");         
      if (flock($fp, LOCK_EX)) { // do an exclusive lock
         mysql_query("update everyonesmoney set money='".intval($_REQUEST["money"])."' where user='$user_id'")
        flock($fp, LOCK_UN); // release the lock
        return true;
     } else {
        return "Couldn't get the lock, it's possible that someone tried to execute query simultaneously!";
     }

   fclose($fp);

   }

Por enquanto, a criação de arquivos é minha abordagem preferida porque:

  1. Acho que é mais rápido criar arquivo local do que acessar banco de dados.

  2. Não preciso adicionar mais uma coluna (carimbo de data / hora) à tabela

  3. Posso modificar facilmente o nome do arquivo para verificar a modificação de coluna específica, ou seja, criar o arquivo "money_user {$ userid} _modified" quando mysqlupdate for concluído.

Isso está certo ou há algo que não entendi?

Foi útil?

Solução

Você pode especificar o valor antigo na cláusula UPDATE da operação WHERE e, em seguida, observar o número de linhas afetadas:

Dado

id  name          amount
--- ------------- ---------
1   Joe User      10

Thread 1 executa

UPDATE accounts SET amount=9 WHERE id=1 AND amount=10;
=> Query Okay, 1 row(s) affected

Thread 2 é executado

UPDATE accounts SET amount=9 WHERE id=1 AND amount=10;
=> Query Okay, 0 row(s) affected

Fora isso, provavelmente implementaria a exclusão um pouco mais cedo, atribuindo tarefas a administradores individuais primeiro, a fim de reduzir a quantidade de tempo desperdiçado.

Outras dicas

No seu caso, suponho que o bloqueio seja a melhor abordagem.Você pode usar bloqueios do MySQL: GET_LOCK, RELEASE_LOCK, IS_FREE_LOCK.As transações, em minha opinião, não garantem que a linha não seja alterada enquanto outro processo executa sua tarefa nos dados obtidos.

Embora seu caso particular não tenha nada a ver com bloqueio no sentido tradicional.IMHO, você precisa registrar suas transações com credenciais e descrições correspondentes, para que seus administradores possam lê-las e não duplicar a mesma modificação de saldo.O bloqueio pode proteger contra modificações simultâneas de linhas, mas não contra mudanças intencionais em caso de dublagem.

Acho que o bloqueio de nível de linha do banco de dados não preenche a situação que você mencionou no primeiro método.Mas também não acho que a criação de arquivos seja mais rápida que acessar o sistema de banco de dados.A criação de arquivos é obviamente mais pesada do que CRUD no banco de dados.

Portanto, sugiro uma abordagem semelhante com a tabela de registro.

  • Cada tabela tem sua própria chave primária (como pid)
  • Registre o nome da tabela e o pid na tabela de registro com carimbo de data / hora quando alguém tenta mexer em uma linha.
  • Verifique a tabela de registro antes de executar uma consulta.

Dê uma olhada no InnoDB e nas transações.Eles são mais adequados para mudanças sensíveis (ou seja, equilíbrio).

Os bancos de dados geralmente são melhores, pois são uma solução centralizada.Se você tiver que escalar devido ao tráfego ou carga de trabalho geral, não é fácil sincronizar esses arquivos.A menos que você não espere nenhuma necessidade de escalonamento e as taxas de E / S sejam boas, está tudo bem.

Permita-me mencionar duas soluções possíveis, que você também pode ter mencionado acima.

Você pode adicionar um "assign_id" com o id de sua conta de administrador combinado com um carimbo de data / hora para que seu aplicativo mostre um aviso se outra pessoa o estiver editando.

Outra solução possível é verificar se alguma alteração foi feita enquanto você preenchia os formulários.Um timestamp last_edited pode ser utilizado aqui.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top