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

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');
}
по логике проверка файлами должна это дело прекратить, но не тут то было ((
 

zerkms

TDD infected
Команда форума
Транзакции рейскондишны не лечат, они только обеспечивают консистентное состояние базы во время транзакции.

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

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

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

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

С.

Продвинутый новичок
попробовал сменить архитектуру БД на innoDB
Инно-исам-шмисам это не архитектура БД, а хранилище. А данная конкретная проблема, я подозреваю, лечится пересмотром именно архитектуры (принципов организации и связи элементов данных), причем без всяких локов и трансакций. Такая сущность как "статус заявки" обычно не бывает жертвой состояния гонки.
 

DelphiWorld

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

Gas

может по одной?
select ... for update - это стандартное решение определенного круга задач, у тебя как раз очень похожий случай.
 

DelphiWorld

Новичок
т.е. если я правильно понял, на примере будет где-то так?
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');
такой вариант правильный?
 

DiMA

php.spb.ru
Команда форума
Поставить лок (и вообще не в БД) в 1000 раз лучше, чем самый тормозной/ресурсоемкий, но очень удобный, select for update. Но в данной задаче лучше так.
Можно легко улучшить ситуацию, отключив в конфиге мыскля оперативную запись транзакций на диск. Будет раз в 10 быстрее (вернее, жесткий диск при большой нагрузке будет ложиться гораздо реже).
 
Сверху