5

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

我正在尝试使用 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

4 回答 4

2

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

您是否正在运行APC(或者您可以)?

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

为了完成这项工作,每个 XHR 请求都需要携带一个(相对)唯一的事务标识符,例如作为表单变量。该标识符将随机生成,或根据当前时间生成,并将在初始 XHR 以及“中止”XHR 中发送,并允许中止请求与正在运行的请求相关联。在下面的示例中,事务标识符的形式为 variable 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 中止结果以通知客户端,“对不起,我来得太晚了。”

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

于 2013-02-20T01:22:41.410 回答
1

让浏览器发回您也存储在数据库中的时间戳或运行编号怎么样。并且您的更新可以检查,以便它仅在新时间戳较新时写入。

于 2013-02-20T01:33:45.940 回答
0

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

于 2013-02-19T21:19:04.220 回答
0

As Marc B pointed, PHP can't detect if the browser is disconnected without sending some output to the browser. The problem is, you have enabled output buffering at the line ob_start('ob_gzhandler'); and that prevents PHP from sending output to the browser.

You either have to remove that line or add a ob_end_* (for example: ob_end_flush()) along with the echo/flush calls.

于 2013-02-22T18:04:25.180 回答