Domanda

A volte capita che due amministratori nel nostro team di supporto stiano cercando di eseguire la stessa operazione sensibile sulla riga della tabella db (diciamo, modificando il valore nella riga). Dobbiamo impedirlo. (Il blocco delle righe non è possibile perché le tabelle sono "myisam")

Ho pensato a diverse soluzioni:

impostando il vecchio valore nel modulo e confrontandolo con quello corrente su submit

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

e poi prima dell'aggiornamento:

   $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;
   }

ma può esserci la seguente situazione:

  1. l'utente ha bisogno che il valore del denaro venga modificato da $ 9 a $ 10

  2. admin1 cambia i suoi soldi in 10 $

  3. l'utente spende in modo intelligente 1 $, quindi il suo denaro attuale diventa di nuovo 9 $!

  4. admin2 cambia il suo denaro a 10 $ senza preavviso.

creazione dell'impostazione del timestamp (colonna updated_at) nella riga

E facendo lo stesso della soluzione 1. Questo ha il vantaggio di dire qualcosa di più del semplice confronto dei dati. Possiamo dire con certezza se i dati sono stati modificati mentre stavamo armeggiando con il modulo o no. svantaggio: non possiamo tenere traccia di quale colonna è stata cambiata esattamente, a meno che non la combiniamo con la soluzione 1

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

e poi durante l'aggiornamento:

   $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;
   }

creazione del file temporaneo di lunghezza 0 con nome specifico dell'oggetto / azione

Crearlo / bloccarlo durante l'aggiornamento e controllarne i file esistenza / datestamp prima dell'aggiornamento.

Prima dell'aggiornamento:

   $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);

   }

Per ora la creazione di file è il mio approccio preferito perché:

  1. Penso che sia più veloce creare file locali che accedere al database.

  2. Non è necessario aggiungere un'altra colonna (timestamp) alla tabella

  3. Posso facilmente modificare il nome del file per verificare la presenza di modifiche specifiche alla colonna, ad esempio creare il file "money_user {$ userid} _modified" quando mysqlupdate è terminato.

È così o c'è qualcosa che fraintendo?

È stato utile?

Soluzione

È possibile specificare il vecchio valore nella clausola UPDATE dell'operazione WHERE, quindi esaminare il numero di righe interessate:

Dato

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

Il thread 1 viene eseguito

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

Il thread 2 viene eseguito

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

A parte questo, probabilmente implementerei l'esclusione un po 'prima, assegnando prima le attività ai singoli amministratori, al fine di ridurre la quantità di tempo sprecata.

Altri suggerimenti

Nel tuo caso, suppongo che il blocco sia l'approccio migliore.Puoi utilizzare i blocchi MySQL: GET_LOCK, RELEASE_LOCK, IS_FREE_LOCK.Le transazioni a mio parere non garantiscono che la riga non venga modificata mentre un altro processo esegue la sua attività sui dati recuperati.

Tuttavia, il tuo caso particolare non ha nulla a che fare con il blocco in senso tradizionale.IMHO, devi registrare le tue transazioni con le credenziali e le descrizioni corrispondenti, in modo che i tuoi amministratori possano leggerle e non duplicare la stessa modifica del saldo.Il blocco può proteggere dalla modifica simultanea delle righe, ma non da modifiche intenzionali in caso di doppiaggio.

Penso che il blocco a livello di riga del database non soddisfi la situazione che hai menzionato nel primo metodo.Ma non credo che la creazione del file sia più veloce dell'accesso al sistema di database, né.La creazione di file è ovviamente più pesante di CRUD sul database.

Quindi, suggerisco un approccio simile con la tabella di registrazione.

  • Ogni tabella ha la propria chiave primaria (come pid)
  • Registra il nome della tabella e il pid nella tabella di log con timestamp quando qualcuno tenta di manipolare una riga.
  • Controlla la tabella di registro prima di eseguire una query.

Dai un'occhiata a InnoDB e alle transazioni.Sono più adatti a cambiamenti sensibili (es. Equilibrio).

I database sono generalmente migliori poiché sono una soluzione centralizzata.Se devi ridimensionare a causa del traffico o in generale del carico di lavoro, non è facile sincronizzare quei file.A meno che non ti aspetti alcun bisogno di ridimensionamento e le velocità di I / O siano buone, va bene.

Consentitemi di menzionare 2 possibili soluzioni, che potreste anche aver menzionato sopra.

Puoi aggiungere un "assign_id" con l'ID del tuo account amministratore combinato con un timestamp in modo che la tua applicazione mostri un avviso se qualcun altro lo sta modificando.

Un'altra possibile soluzione è controllare se sono state apportate modifiche durante la compilazione dei moduli.Un timestamp last_edited potrebbe essere utilizzato qui.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top