Question

Il arrive parfois que deux administrateurs de notre équipe d'assistance essaient de faire la même opération sensible sur la ligne de table DB (disons, modifiant la valeur dans la ligne). Nous devons empêcher cela. (Le verrouillage des lignes n'est pas possible car les tables sont "myisam")

J'ai pensé à plusieurs solutions:

définir l'ancienne valeur dans le formulaire et la comparer avec la soumission actuelle

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

puis avant de mettre à jour:

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

Mais il peut y avoir une situation suivante:

  1. L'utilisateur a besoin de valeur monétaire pour passer de 9 $ à 10 $

  2. Admin1 change son argent à 10 $

  3. L'utilisateur dépense intelligemment 1 $, donc son argent actuel redonne encore 9 $!

  4. Admin2 change son argent à 10 $ sans avertissement.

Création du paramètre Timestamp (Updated_at Colonne) dans la ligne

Et faire de même que dans la solution 1. Cela a un avantage qu'il dise plus qu'une simple comparaison de données. Nous pouvons dire avec certitude si les données ont été modifiées pendant que nous jouions avec le formulaire ou non. désavantage - nous ne pouvons pas suivre la colonne exactement modifiée, sauf si nous la combinons avec la solution 1

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

Et puis pendant la mise à jour:

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

Création du fichier temporaire de 0-longueur avec un nom spécifique à l'objet / à l'action

La créer / verrouiller pendant la mise à jour et vérifier son existence / datestamp avant la mise à jour.

Avant la mise à jour:

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

   }

Pour l'instant, la création de fichiers est mon approche préférée car:

  1. Je pense qu'il est plus rapide de créer un fichier local que d'accès à la base de données.

  2. Je n'ai pas besoin d'ajouter une colonne de plus (horodatage) à la table

  3. Je peux facilement modifier le nom de fichier pour vérifier la modification spécifique de la colonne, c'est-à-dire créer un fichier "Money_User {$ userId} _Modified" Lorsque MySqLupDate est terminé.

Est-ce que c'est vrai ou y a-t-il quelque chose que je mal compris?

Était-ce utile?

La solution

Vous pouvez spécifier l'ancienne valeur dans le UPDATE opération WHERE clause, puis regardez le nombre de lignes affectées:

Donné

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

Le thread 1 exécute

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

Le thread 2 exécute

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

En dehors de cela, j'implémenterais probablement l'exclusion un peu plus tôt, en attribuant d'abord des tâches aux administrateurs individuels, afin de réduire le temps perdu.

Autres conseils

Dans votre cas, je suppose que le verrouillage est la meilleure approche. Vous pouvez utiliser MySQL Locks: Get_lock, release_lock, is_free_lock. Les transactions à mon avis ne garantissent pas que la ligne ne changera pas tandis qu'un autre processus effectuant sa tâche sur les données récupérées.

Cependant, votre cas particulier n'a rien à voir avec le verrouillage au sens traditionnel. À mon humble avis, vous devez enregistrer vos transactions avec des informations d'identification et des descriptions correspondantes, afin que vos administrateurs puissent les lire et ne pas surnommer la même modification du solde. Lock peut protéger de la modification simultanée des lignes, mais pas du changement intentionnel en cas de doublage.

Je pense que le verrouillage au niveau des lignes de la base de données ne remplit pas la situation que vous avez mentionnée dans la première méthode. Mais je ne pense pas que la création de fichiers soit plus rapide que d'accès au système de base de données, non plus. La création de fichiers est évidemment plus lourde que Crud sur la base de données.

Donc, je suggère une approche similaire avec une table de journalisation.

  • Chaque table a sa propre clé primaire (comme pid)
  • Enregistrez le nom du tableau et le PID dans la table de journal avec l'horodatage lorsque quelqu'un tente de jouer une ligne.
  • Vérifiez la table de journal avant d'exécuter une requête.

Jetez un œil à InNODB et aux transactions. Ils conviennent plus aux changements sensibles (c'est-à-dire l'équilibre).

Les bases de données sont généralement meilleures car elles sont une solution centralisée. Si vous devez évoluer en raison du trafic ou généralement du chargement, il n'est pas facile de synchroniser ces fichiers. À moins que vous ne vous attendiez à aucun besoin d'échelle et que les taux d'E / S soient bons, c'est OK.

Permettez-moi de mentionner 2 solutions possibles, que vous pourriez également avoir mentionnées ci-dessus.

Vous pouvez ajouter un "Assigned_id" avec l'ID de votre compte d'administration combiné avec un horodatage afin que votre application affiche un avertissement si quelqu'un d'autre le modifie.

Une autre solution possible consiste à vérifier si des modifications ont été apportées pendant que vous remplissiez vos formulaires. Un horodatage Last_edit pourrait être utilisé ici.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top