Pregunta

A veces sucede que dos administradores de nuestro equipo de soporte intentan realizar la misma operación sensible en la fila de la tabla db (digamos, modificando el valor en la fila). Necesitamos prevenir eso. (El bloqueo de filas no es posible porque las tablas son "myisam")

He pensado en varias soluciones:

establecer el valor anterior en el formulario y compararlo con el actual en el envío

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

y luego antes de actualizar:

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

pero puede haber la siguiente situación:

  1. el usuario necesita que el valor monetario se cambie de 9 $ a 10 $

  2. admin1 cambia su dinero a 10 $

  3. el usuario gasta inteligentemente 1 $, por lo que su dinero actual vuelve a ser de 9 $.

  4. admin2 cambia su dinero a 10 $ sin previo aviso.

creación de la configuración de marca de tiempo (columna actualizada_en) en la fila

Y hacer lo mismo que en la solución 1. Esto tiene la ventaja de que dice más que una simple comparación de datos. Podemos decir con certeza si los datos se cambiaron mientras estábamos jugando con el formulario o no. desventaja: no podemos rastrear qué columna se cambió exactamente, a menos que la combinemos con la solución 1

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

y luego durante la actualización:

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

creando el archivo temporal de longitud 0 con un nombre específico de objeto / acción

Crearlo / bloquearlo durante la actualización y verificar su existencia / marca de fecha antes de la actualización.

Antes de la actualización:

   $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 ahora, la creación de archivos es mi enfoque preferido porque:

  1. Creo que es más rápido crear un archivo local que acceder a una base de datos.

  2. No necesito agregar una columna más (marca de tiempo) a la tabla

  3. Puedo modificar fácilmente el nombre del archivo para verificar la modificación de una columna específica, es decir, crear el archivo "money_user {$ userid} _modified" cuando mysqlupdate esté listo.

¿Es correcto o hay algo que no entiendo?

¿Fue útil?

Solución

Puede especificar el valor anterior en la cláusula UPDATE de la operación WHERE, y luego observar el número de filas afectadas:

Dado

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

El hilo 1 se ejecuta

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

El hilo 2 se ejecuta

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

Aparte de eso, probablemente implementaría la exclusión un poco antes, asignando tareas a administradores individuales primero, para reducir la cantidad de tiempo perdido.

Otros consejos

En su caso, supongo que bloquear es el mejor enfoque.Puede utilizar bloqueos de MySQL: GET_LOCK, RELEASE_LOCK, IS_FREE_LOCK.En mi opinión, las transacciones no garantizan que la fila no cambie mientras otro proceso realiza su tarea con los datos obtenidos.

Aunque su caso particular no tiene nada que ver con el bloqueo en el sentido tradicional.En mi humilde opinión, debe registrar sus transacciones con las credenciales y descripciones correspondientes, para que sus administradores puedan leerlas y no copiar la misma modificación de saldo.El bloqueo puede proteger de modificaciones de filas simultáneas, pero no de cambios intencionales en caso de doblaje.

Creo que el bloqueo a nivel de fila de la base de datos no cumple con la situación que mencionaste en el primer método.Pero tampoco creo que la creación de archivos sea más rápida que acceder al sistema de base de datos.La creación de archivos es obviamente más pesada que CRUD en la base de datos.

Entonces, sugiero un enfoque similar con la tabla de registro.

  • Cada tabla tiene su propia clave principal (como pid)
  • Registre el nombre de la tabla y el pid en la tabla de registro con la marca de tiempo cuando alguien intente manipular una fila.
  • Verifique la tabla de registro antes de ejecutar una consulta.

Eche un vistazo a InnoDB y las transacciones.Son más adecuados para cambios sensibles (es decir, equilibrio).

Las bases de datos son generalmente mejores ya que son una solución centralizada.Si tiene que escalar debido al tráfico o la carga de trabajo en general, no es fácil sincronizar esos archivos.A menos que no espere ninguna necesidad de escalado y las tasas de E / S sean buenas, está bien.

Permítame mencionar dos posibles soluciones, que también podría haber mencionado anteriormente.

Puede agregar un "asignado_id" con el identificador de su cuenta de administrador combinado con una marca de tiempo para que su aplicación muestre una advertencia si alguien más la está editando.

Otra posible solución es verificar si se han realizado cambios mientras llenaba sus formularios.Aquí se podría utilizar una marca de tiempo last_edited.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top