Кошмар или состояние гонки

DelphiWorld

Новичок
Есть 2 класса.
Первый usersи status , в users есть свойство zayavka, которое получает в status, при вызове в несколько потоков возникает состояние гонки
метод в status примерно такой
PHP:
private function Start($id)
{
  if(isset($this->user->zayavka['id']))
  {
     //тут расчеты
    mysql_query('INSERT INTO ...');
    mysql_query('DELETE FROM `table_name` WHERE `id`="'.$id.'"');
    unset($this->user->zayavka['id']);
  }
}
запуская в несколько потоков метод вызывается несколько раз, соответсвенно выполняется 2 инсерта ((

попробовал сменить архитектуру БД на innoDB
все это добро заключил в транзакцию
PHP:
	mysql_query('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
	mysql_query('START TRANSACTION');
        ...
       mysql_query('COMMIT');

подскажите что я делаю не так? ))

ошибке пол года, весь интернет облазил, ничего не помогает...
пробовал файлы делать типо
PHP:
if(!file_exists('fname.lock')) {
touch('fname.lock');
//код
unlink('fname.lock');
!file_exists('fname.lock');
}
по логике проверка файлами должна это дело прекратить, но не тут то было ((
 
Транзакции рейскондишны не лечат, они только обеспечивают консистентное состояние базы во время транзакции.

Чтобы синхронизировать потоки в "нормальных" приложениях используются мутексы-локи-семафоры, а в вебе тебе скорее всего и лучше всего использовать блокировку на уровне базы.

Например что-нибудь вроде SELECT ... FOR UPDATE (после твоего if'а)

Подробнее - только после детального описания, а то ты даже запросы повыпиливал.

Если всё таки хочешь на файлах делать - тогда тебе нужно flock($lock, LOCK_EX), а то у тебя точно такой же рейскондишн и при твоих file_exists/touch получается. Причём блокировку нужно ставить **ДО** проверки, либо делать двойную проверку (не будут тратиться ресурсы на блокировку).
 
попробовал сменить архитектуру БД на innoDB

Инно-исам-шмисам это не архитектура БД, а хранилище. А данная конкретная проблема, я подозреваю, лечится пересмотром именно архитектуры (принципов организации и связи элементов данных), причем без всяких локов и трансакций. Такая сущность как "статус заявки" обычно не бывает жертвой состояния гонки.
 
zerkms
спасибо за развернутый ответ )
про локи в базе еще сомневаюсь, т.к. это зло, но.. видимо другого решения тут и нет
 
select ... for update - это стандартное решение определенного круга задач, у тебя как раз очень похожий случай.
 
т.е. если я правильно понял, на примере будет где-то так?
PHP:
private function battleFinish($id,$hp1,$hp2)
	{
		mysql_query('START TRANSACTION');
		$btl = mysql_fetch_array(mysql_query('SELECT * FROM `battle` WHERE `id`="'.$id.'" AND `time_over`="0" LIMIT 1 FOR UPDATE'));
		if(isset($btl['time_over']))
		{			
			$btl['time_over'] = time();
			mysql_query('UPDATE `battle` SET `time_over`="'.time().'" WHERE `id`="'.$btl['id'].'"');
                        /*
                       код
                       */
                        mysql_query('UPDATE `battle` SET `team_win`="'.$btl['team_win'].'" WHERE `id`="'.$btl['id'].'"');
			mysql_query('INSERT INTO `battle_logs` (`id_battle`,`act_texts`,`time`,`acts`) VALUES ("'.$btl['id'].'","'.$txt.'","'.time().'","0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0")');		
			mysql_query('INSERT INTO `battle_finish` (`id_battle`,`time`,`screen_team1`,`screen_team2`) VALUES ("'.$btl['id'].'","'.time().'","'.$scr_1.'","'.$scr_2.'")');
			$this->players->getLoadData();			
		} else {
		$btl['time_over'] = time();
		}
		mysql_query('COMMIT');
такой вариант правильный?
 
Поставить лок (и вообще не в БД) в 1000 раз лучше, чем самый тормозной/ресурсоемкий, но очень удобный, select for update. Но в данной задаче лучше так.
Можно легко улучшить ситуацию, отключив в конфиге мыскля оперативную запись транзакций на диск. Будет раз в 10 быстрее (вернее, жесткий диск при большой нагрузке будет ложиться гораздо реже).
 
Сверху