Php 当用户中止连接时回滚事务

Php 当用户中止连接时回滚事务,php,mysql,ajax,rollback,abort,Php,Mysql,Ajax,Rollback,Abort,首先,为我的英语感到抱歉,我是在谷歌翻译的帮助下写的 我正在尝试用PHP和Ajax制作一个像GoogleWave这样的应用程序。我有一个textarea,当用户输入内容时,页面上的javascript会通过oninput检测到,并将textarea的内容发送到服务器,服务器将内容存储到数据库中 我所做的是,每次通过XHR发送内容时,总会有XHR.abort()中断先前的XHR请求。数据库中的数据很好,但是,有时它存储在以前的版本中 我知道会发生这种情况,因为PHP没有停止执行,即使客户端已经中止

首先,为我的英语感到抱歉,我是在谷歌翻译的帮助下写的

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

我所做的是,每次通过XHR发送内容时,总会有
XHR.abort()
中断先前的XHR请求。数据库中的数据很好,但是,有时它存储在以前的版本中

我知道会发生这种情况,因为PHP没有停止执行,即使客户端已经中止,有时上一个请求比上一个请求花费更多的时间,并且在上一个请求之后完成,所以我阅读了“忽略用户中止”和“连接中止”的函数手册,但问题仍然存在

我创建这个脚本是为了模拟这种情况,我希望当我中止连接(按“停止”,关闭选项卡/窗口)时,数据库中没有任何新数据,但5秒钟后,我仍然有新数据,因此当用户中止连接时,我需要帮助回滚事务

下面是要模拟的脚本(定义了PDO_DSN、PDO_用户、PDO_PASS):


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

在Oracle中,您可以使用PL/SQL执行MERGE IF MATCH THEN INSERT命令,因此您可以在一个查询中处理此问题,但在MySQL中,您最好使用两个查询—一个查询此用户的现有记录,另一个查询在不匹配时插入。

如果您正在查找
XHR.abort()
连接\u aborted())< /代码>不可靠,考虑其他方式发送带外信号,告知正在运行的PHP请求,它不应该提交事务。

你在跑步吗(或者你会跑步吗)

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

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

“中止”XHR处理程序示例:

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

apc_store($uniqueTransactionId, 1, 15);
<?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();

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

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


您要么删除该行,要么添加一个ob_end_*(例如:
ob_end_flush()
)以及echo/flush调用。

如果设置
ignore_user_abort(false)
?@explosionPill问题仍然存在,并且
connection_aborted()
无法与
ignore_user_abort(false)一起工作
php无法可靠地检测连接是否/何时中止,直到您尝试执行输出。请参阅注释:@MarcB I执行了输出,请参阅代码的第11行。问题是
$PDO=new PDO…
为当前PHP脚本创建连接。不能从另一个PHP脚本中断当前执行。您可以使用一个脚本将一个tmp文件写入磁盘,然后检查tmp文件是否丢失并回滚。任何脚本都可以删除tmp文件。在没有
ob\u start()
的情况下已经尝试过,并且在
echo
一些文本之后只使用
flush()
,但仍然无法工作。
ob\u flush()
也会刷新输出缓冲区,如果它是在php.ini上设置的——您是否使用
ini\u set('output\u buffering',false)进行过尝试
或同时使用
flush()
ob\u flush()
(即使没有
ob\u start()
)?是的,我尝试使用
ini\u集(“输出缓冲”,false)
ini_集('output_buffering','1')没有成功。+1谢谢你的回复,你的想法和我读了埃文的答案后得到的想法很相似。:)+1个有趣的想法,您使用了相同的原理,但没有调用
connection\u aborted()
,而是使用了另一个信号来回滚更改,但使用其他额外请求仍然存在计时问题,iWantSimpleLife的想法最接近于解决方案,然而,我会给你100的声誉为你的延伸和完整的答案。
<?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();