用户中止连接时回滚事务

时间:2013-02-15 01:15:55

标签: php mysql ajax rollback abort

首先,对不起我的英语,我在谷歌翻译的帮助下写了这篇文章。

我正在尝试使用PHP和Ajax创建像Google Wave这样的应用程序。我有一个textarea,当用户输入内容时,页面上的javascript被oninput检测到,并将textarea的内容发送到服务器,服务器将内容存储到数据库中。

我正在做的是每当我通过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();

4 个答案:

答案 0 :(得分:2)

如果您发现XHR.abort()connection_aborted()不可靠,请考虑使用其他方式发送带外信号,以通知正在运行的PHP请求它不应提交事务。

你在运行APC(或者你可以)?

您可以发送另一个发出中止的XHR请求,而不是调用XHR.abort()。此请求的目的是在APC用户缓存中记录特殊密钥。此密钥的存在将指示正在运行的PHP请求它应该回滚。

为了使这项工作,每个XHR请求都需要携带(相对)唯一的事务标识符,例如,作为表格变量。该标识符将随机生成,或基于当前时间生成,并将在初始XHR以及“中止”XHR中发送,并允许中止请求与正在运行的请求相关联。在下面的示例中,事务标识符的格式为变量t

示例“abort”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中止结果以通知客户端,“抱歉,我来不及。”

请记住,如果您的应用程序托管在多个实时服务器上,您将需要使用共享缓存,例如memcachedredis,因为APC的缓存仅在单个进程上共享机。

答案 1 :(得分:1)

如何让浏览器发回您还存储在数据库中的时间戳或正在运行的号码。并且您的更新可以检查,以便只有在新时间戳更新时才会写入。

答案 2 :(得分:0)

我已经多次使用Javascript和Ajax看过这个问题。如果您在实现UI时不是非常小心,那么用户可以单击两次和/或浏览器可以触发两次ajax调用,从而导致相同的记录两次击中数据库。这些可能是来自UI的两个完全不同的http请求,因此您需要确保服务器端代码可以在将它们插入数据库之前过滤掉重复项。 我的解决方案通常是查询最近由同一用户输入的记录,并检查这是否真的是新条目。如果此新记录不在数据库中,则插入它,否则忽略。 在Oracle中,您可以使用PL / SQL来使用MERGE IF MATCH THEN INSERT命令,因此您可以在一个查询中处理此问题,但在MySQL中,最好使用两个查询 - 一个查询此用户的现有记录,然后如果没有匹配则插入另一个。

答案 3 :(得分:0)

正如Marc B指出的那样,如果没有向浏览器发送一些输出,PHP无法检测浏览器是否已断开连接。问题是,您已在行ob_start('ob_gzhandler');启用了输出缓冲,这会阻止PHP将输出发送到浏览器。

您必须删除该行或添加ob_end_ *(例如:ob_end_flush())以及echo / flush调用。