2013-02-15 4 views
4

Прежде всего, извините за мой английский, я написал это с помощью Google Translate.Откат транзакции, когда пользователь прерывает соединение

Я пытаюсь сделать приложение, подобное Google Wave, с PHP и Ajax. У меня есть textarea, что когда пользователь вводит что-то, javascript на странице обнаруживается с oninput и отправляет содержимое текстового поля на сервер, а сервер сохраняет содержимое в базе данных.

Что я делаю, так это то, что каждый раз, когда я отправляю контент по XHR, есть XHR.abort(), который всегда прерывает предыдущий запрос XHR. Данные, находящиеся в базе данных, являются точными, однако иногда они сохраняются в предыдущей версии.

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

Я создал этот скрипт, чтобы имитировать ситуацию, и я надеялся, когда я прервал соединение (нажмите «остановить», закройте вкладку/окно), никаких новых данных в базе данных нет, но через 5 секунд У меня есть новые данные, поэтому мне нужна помощь в откате транзакции, когда пользователь прерывает соединение.

Вот скрипт для моделирования (PDO_DSN, PDO_USER, PDO_PASS определены):

<?php 
ignore_user_abort(true); 

ob_start('ob_gzhandler'); 

$PDO = new PDO(PDO_DSN, PDO_USER, PDO_PASS, array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8')); 

$PDO->beginTransaction(); 
$query = $PDO->query('INSERT INTO `table` (`content`) VALUES (' . $PDO->quote('test') . ')'); 
sleep(5); 
echo ' '; 
ob_flush(); 
flush(); 
if (connection_aborted()) { 
    $PDO->rollBack(); 
    exit; 
} 
$PDO->commit(); 

ob_end_flush(); 
+0

Что делать, если вы задали 'ignore_user_abort (false)'? –

+0

@ExplosionPills Проблема сохраняется, и 'connection_aborted()' не работает с 'ignore_user_abort (false)' –

+1

php не может достоверно определить, когда/когда соединение прерывается, пока вы не попытаетесь выполнить вывод. См. Примечания: http://www.php.net/manual/en/function.ignore-user-abort.php –

ответ

1

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

+0

+1 Спасибо за ваш ответ, ваша идея была похожа на то, что я получил после того, как я прочитал ответ Эвана. :) –

0

Я видел этот вопрос много раз с Javascript и Ajax. Если вы не очень осторожны в реализации пользовательского интерфейса, пользователь может дважды щелкнуть дважды, и/или браузер может вызвать вызов ajax дважды, в результате чего одна и та же запись ударит вашу базу данных дважды. Это могут быть два совершенно разных HTTP-запроса от вашего пользовательского интерфейса, поэтому вам нужно убедиться, что ваш код на стороне сервера может отфильтровывать дубликаты перед их вставкой в ​​базу данных. Мое решение, как правило, запрашивать записи, введенные одним и тем же пользователем в последнее время, и проверить, действительно ли это новая запись или нет. Если эта новая запись еще не находится в базе данных, тогда вставьте ее, иначе игнорируйте. В Oracle вы можете использовать PL/SQL для команды MERGE IF MATCH THEN INSERT, поэтому вы можете справиться с этим в одном запросе, но в MySQL вам лучше использовать два запроса - один для запроса существующих записей этого пользователя и затем другой, чтобы вставить, если нет совпадения.

2

Если вы находите XHR.abort() и connection_aborted() ненадежными, рассмотрите другие способы отправки внеполосного сигнала, чтобы сообщить запущенному PHP-запросу, что он не должен совершать транзакцию.

Вы используете APC (или не могли бы быть)?

Вместо вызова XHR.abort() вы можете отправить еще один запрос XHR, сигнализирующий о прерывании. Цель этого запроса состояла бы в записи специального ключа в кеш пользователя APC. Присутствие этого ключа указывает на выполнение PHP-запроса, что он должен откатиться.

Для выполнения этой работы каждый запрос XHR должен иметь (относительно) уникальный идентификатор транзакции, например. как переменную формы. Этот идентификатор будет генерироваться случайным образом или основан на текущем времени и будет отправлен в начальном XHR, а также в «прерванном» XHR и разрешит запрос прерывания быть скоррелированным с запросом на выполнение. В приведенном ниже примере идентификатор транзакции имеет форму переменной t.

Пример "прервать" обработчик XHR:

<?php 
$uniqueTransactionId = $_REQUEST['t']; 
$abortApcKey = 'abortTrans_' . $uniqueTransactionId; 

apc_store($uniqueTransactionId, 1, 15); 

Пример пересмотрела записи базы данных XHR обработчик:

<?php 
$PDO = new PDO(PDO_DSN, PDO_USER, PDO_PASS, 
       array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8')); 

$PDO->beginTransaction(); 
$query = $PDO->query('INSERT INTO `table` (`content`) VALUES (' . $PDO->quote('test') . ')'); 

$uniqueTransactionId = $_REQUEST['t']; 
$abortApcKey = 'abortTrans_' . $uniqueTransactionId; 
if (apc_exists($abortApcKey)) { 
    $PDO->rollBack(); 
    exit; 
} 

$PDO->commit(); 

Вы можете все еще есть проблемы синхронизации. Отмена может все же появиться слишком поздно, чтобы остановить фиксацию. Чтобы справиться с этим изящно, вы можете изменить обработчик записи базы данных для записи ключа APC, указывающего, что транзакция была выполнена. Обработчик прерывания может затем проверить существование этого ключа и отправить обратно значимый результат прерывания XHR, чтобы сообщить клиенту: «Извините, я опоздал».

Имейте в виду, что если ваше приложение размещено на нескольких серверах в реальном времени, вы захотите использовать общий кэш, такой как memcached или redis, так как кэш APC используется только для процессов на одной машине.

+0

+1 Интересная идея, вы использовали тот же принцип, но вместо этого вызова 'connection_aborted()', вы использовали другой сигнал, чтобы отменить изменение, но по-прежнему есть проблема с синхронизацией, используя другой дополнительный запрос, идея iWantSimpleLife была наиболее близкой для решения, однако я собираюсь дать вам 100 репутацию для вашего расширенного и полного ответа. –

0

Как указал Марк Б, PHP не может обнаружить, отключен ли браузер, не отправив какой-либо вывод в браузер. Проблема в том, что вы включили буферизацию вывода в строке ob_start('ob_gzhandler'); и это предотвращает отправку вывода PHP в браузер.

Вы также должны удалить эту строку или добавить ob_end_ * (например: ob_end_flush()) вместе с вызовами echo/flush.

+0

Уже пробовал без 'ob_start()' и использовал только 'flush()' после 'echo' некоторый текст, но все равно не работал. –

+0

'ob_flush()' также удаляет выходной буфер, если он установлен на вашем php.ini - вы пробовали его с помощью 'ini_set ('output_buffering', false);' или с ** оба ** 'flush()' и 'ob_flush()' (даже без 'ob_start()')? –

+0

Да, я пробовал с 'ini_set ('output_buffering', false);' и 'ini_set ('output_buffering', '1');' без успеха. –

Смежные вопросы