Предотвращение одновременного доступа к строке таблицы БД - PullRequest
0 голосов
/ 16 января 2012

Иногда бывает, что два администратора в нашей группе поддержки пытаются выполнить одну и ту же чувствительную операцию над строкой таблицы БД (скажем, изменяя значение в строке).Мы должны предотвратить это.(Блокировка строки невозможна, потому что таблицы «myisam»)

Я думал о нескольких решениях:

установка старого значения в форме и сравнение его с текущим при отправке

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

, а затем перед обновлением:

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

, но может быть следующая ситуация:

  1. пользователю необходимо изменить денежную стоимость с 9 $на 10 $

  2. admin1 меняет свои деньги на 10 $

  3. пользователь ловко тратит 1 $, поэтому его текущие деньги снова становятся 9 $!

  4. admin2 меняет свои деньги на 10 $ без предупреждения.

создание установки метки времени (столбец updated_at) в строке

И делает то же самое, что и в решении 1. Это имеет то преимущество, что говорит больше, чем простое сравнение данных.Мы можем точно сказать, изменились ли данные, пока мы возились с формой, или нет.недостаток - мы не можем отследить, какой именно столбец был изменен, если мы не объединим его с решением 1

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

, а затем при обновлении:

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

создаем временный файл 0-длины с объектом/ имя конкретного действия

Создание / блокировка во время обновления и проверка его существования / метка даты перед обновлением.

Перед обновлением:

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

   }

Для файла сейчассоздание - мой предпочтительный подход, потому что:

  1. Я думаю, что создавать локальный файл быстрее, чем базу данных доступа.

  2. Мне не нужно добавлятьеще один столбец (отметка времени) в таблице

  3. Я могу легко изменить имя файла, чтобы проверить наличие конкретной модификации столбца, т.е. создать файл "money_user {$ userid} _modified", когда mysqlupdate будет выполнен.

Это правильно или я что-то не так понял?

Ответы [ 4 ]

1 голос
/ 16 января 2012

Вы можете указать старое значение в предложении UPDATE операции WHERE, а затем посмотреть количество затронутых строк:

Задано

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

Поток 1 выполняется

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

Выполняется поток 2

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

Кроме этого, я бы, вероятно, реализовал исключение чуть раньше, сначала назначив задачи отдельным администраторам, чтобы уменьшить количествовремя потеряно.

1 голос
/ 16 января 2012

Я думаю, что блокировка на уровне строки базы данных не соответствует ситуации, которую вы упомянули в первом методе.Но я не думаю, что создание файлов происходит быстрее, чем доступ к системе баз данных.Создание файла явно тяжелее, чем CRUD в базе данных.

Итак, я предлагаю аналогичный подход с таблицей журналов.

  • Каждая таблица имеет свой собственный первичный ключ (например, pid)
  • Запишите имя таблицы иpid в таблицу журнала с отметкой времени, когда кто-то пытается изменить строку.
  • Проверьте таблицу журнала перед выполнением запроса.
1 голос
/ 16 января 2012

В вашем случае, я полагаю, блокировка - лучший подход.Вы можете использовать блокировки MySQL: GET_LOCK, RELEASE_LOCK, IS_FREE_LOCK .Транзакции, на мой взгляд, не гарантируют, что строка не изменится, пока другой процесс выполняет свою задачу по извлеченным данным.

Хотя ваш конкретный случай не имеет ничего общего с блокировкой в ​​традиционном смысле.ИМХО, вам нужно регистрировать транзакции с соответствующими учетными данными и описаниями, чтобы ваши администраторы могли их читать, а не дублировать изменения баланса.Блокировка может защитить от одновременной модификации строки, но не от преднамеренного изменения в случае дублирования.

0 голосов
/ 16 января 2012

Посмотрите на InnoDB и транзакции.Они больше подходят для чувствительных изменений (т. Е. Баланса).

Базы данных, как правило, лучше, поскольку они являются централизованным решением.Если вам нужно масштабировать из-за трафика или из-за рабочей нагрузки, синхронизировать эти файлы нелегко.Если вы не ожидаете необходимости масштабирования и скорости ввода-вывода будут хорошими, это нормально.

Позвольте мне упомянуть 2 возможных решения, которые вы также могли упомянуть выше.

Вы можете добавить «assign_id» с идентификатором вашей учетной записи администратора в сочетании с отметкой времени, чтобы ваше приложение показывало предупреждение, если его редактирует кто-то другой.

Другое возможное решение - проверить, были ли внесены какие-либо изменения.сделано, пока вы заполняли свои формы.Здесь может быть использована последняя отметка времени.

...