Предотвращение одновременного доступа к строке таблицы db

Иногда бывает, что два администратора в нашей команде поддержки пытаются выполнить одну и ту же чувствительную операцию в строке таблицы db (скажем, изменяя значение в строке). Мы должны это предотвратить. (Блокировка строк невозможна, поскольку таблицы являются «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 $ без предупреждения.

создание отметки времени (update_at column) в строке

И делать то же, что и в решении 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 с именем объекта / действия

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

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

  $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 будет выполнен.

Это правильно или есть что-то, что я неправильно понимаю?

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

Данный

 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 

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

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

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

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

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

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

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

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

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

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

Еще одно возможное решение – проверить, были ли сделаны какие-либо изменения во время заполнения ваших форм. Здесь можно использовать временную метку last_edited.