【问题标题】:Stop query through pdo通过 pdo 停止查询
【发布时间】:2014-12-31 10:28:08
【问题描述】:

我正在使用 PDO,用户应该可以选择停止他之前触发的请求。

例如,现在我单击生成报告,但是在请求之后我忘记选择另一个使报告无用的字段。所以我需要取消请求,然后重新创建一个。

基本上,如何取消正在运行的 MYSQL 查询?

if (!isset($_POST['cancel_request'])) { 
    //query
}else{
    //the user cancel the query
}

我知道我可以使用kill命令和PID进程,但这应该通过PDO运行,我不知道PID是什么。

【问题讨论】:

  • PDO::rollBack 是否不适用于您的方案?
  • 为什么必须终止查询?为什么不让SELECT 完成并忽略结果?
  • 终止查询假定您异步生成报告,即您启动查询,返回其 id,并每 n 秒轮询一次结果。在这种情况下,您应该已经有一些 id。否则,停止查询并不是您遇到的唯一问题。
  • @bishop,好的,但是对于包含无用数据的大型查询或待处理的多个查询没有意义。

标签: mysql database pdo


【解决方案1】:

正如其他人所说,您可以登录到您的 MySQL 服务器,发出 SHOW PROCESSLIST,找到要放弃的查询的编号,然后发出 KILL number 命令。

您似乎希望此功能为您的报表设计用户提供自助服务,而不是让他们打电话给您网络运营中的某个人并要求他们这样做,或者教他们使用管理工具。

我以前做过这种事情。它需要三个部分。

首先,您需要安排在用户可能希望放弃的查询类型中插入标签。例如,如果您的用户正在这样做

SELECT cust_id, SUM(orders), COUNT(orders)
  FROM big_fat_table
 GROUP BY cust_id

您需要更改查询的文本,使其在评论中嵌入标签,例如这样。

SELECT /*report:user2290084*/ cust_id, SUM(orders), COUNT(orders)
  FROM big_fat_table
 GROUP BY cust_id

请注意,此标签中有两个项目:report: 和一个用户 ID。用户 ID 必须与运行查询的连接无关——如果您试图放弃在其上运行的查询,该连接将被绑定。该标签根本不影响查询的执行。它只是显示在服务器的进程表中。

第二:运行这些查询的 PDO 代码将需要良好且用户友好的异常处理,因为让查询在您的代码下面爆炸将成为例行公事。您将需要对此进行试验,以使此放弃查询功能对您的用户有用。

第三:当你需要放弃对user2290084的报表查询时发出这个查询

select id from information_schema.processlist where info like '%/*%report:user2290084*/%'

它通过进程列表查找带有适当标记的查询的进程 ID。然后发出

kill <<that process id>>

你已经放弃了查询。

在 php 中,它可能看起来像这样:

$q = 'select id from information_schema.processlist where info like :tag';
$tag = '%/*report:' . $userid . '*/%'
$stmt = $dbh->prepare($q);
$stmt->bindParam(':tag', $tag);
$stmt->execute();
$pids = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);

foreach ($pid in $pids) {
    $dbh->query('KILL ' . $pid);
}        

请注意,此代码中有一个循环。您可能会为此用户运行多个报告查询,或者没有。

【讨论】:

  • 在查询中使用识别注释的好主意。
  • 这里有一个问题:报告生成脚本重新连接并尝试完成其工作,或者在连接中断时中止。在第一种情况下,杀戮脚本没有效果。在第二种情况下,它可能是执行此操作的终止脚本,也可能是真正的连接问题或 MySQL 服务器上的问题。找出来并不容易。两个脚本(生成报告的脚本和停止报告的脚本)需要一种通信方式。
  • 没错,@axiac。基于此的完全产品化和可扩展的系统将需要围绕报告脚本进行大量包装工作。我在谈到“良好而友好的异常处理”时有点暗示了这一点。
【解决方案2】:

这里的主要问题是在生成报告的异步请求和应该停止它的脚本之间共享 PID。

您可以使用以下方法获取您的 PID:

    $stmt = $dbh->prepare("SELECT CONNECTION_ID()");
    $stmt->execute();
    $pid = $stmt->fetchColumn();

您可以使用php-shared-memory 之类的东西在脚本之间创建共享变量。如果您的项目依赖项没有使用 Composer,则此库有一个独立版本(1.5.0,here)。

实施示例:

<?php

if (!include __DIR__ . '/vendor/autoload.php')
{
    die('You must set up the project dependencies.');
}

use Fuz\Component\SharedMemory\SharedMemory;
use Fuz\Component\SharedMemory\Storage\StorageFile;

// your intializations here

$storage = new StorageFile("/tmp/shared.{$user_id}.sync");
$shared = new SharedMemory($storage);

if (!isset($_POST['cancel_request']))
{
    $stmt = $dbh->prepare("SELECT CONNECTION_ID()");
    $stmt->execute();
    $pid = $stmt->fetchColumn();

    $shared->pid = $pid;

    // your long query here

    $shared->destroyStorage();
}
else
{
    // kills pid
    $pid = $shared->pid;
    if (!is_null($pid))
    {
        $dbh->exec("KILL {$pid}");
    }
}

【讨论】:

  • 如果两个用户想要生成一个报告会发生什么?
  • 正如您在我的示例中看到的,我使用假设的$user_id 来区分共享资源。这意味着每个用户的共享对象实例都是不同的。无论如何,我让开发人员调整代码以满足他的需求。
  • 这正是因为您正在使用您的 ajax 请求无法并行运行的会话。停止查询的请求会延迟到释放会话文件以避免并发访问。该行为已被广泛记录,例如参见this thread
  • ok Alain,我会尝试使用 php 共享内存。对删除感到抱歉,但我正在测试会话,我不想发表描述不正确的评论。
  • @AlainTiemblo,插件源代码中不存在$shared-&gt;removeStorage();方法。我是对的吗?应该使用$shared-&gt;remove();
【解决方案3】:

由于您已经在运行查询并进行第二次事务以取消它,因此您需要了解有关正在运行的查询的一些信息。

http://dev.mysql.com/doc/refman/5.1/en/show-processlist.html

Show processlist 将显示所有查询,找到正确的并发出kill 命令(在 MySQL 中)

http://dev.mysql.com/doc/refman/5.1/en/kill.html

不过,您将需要一些信息来匹配。信息框显示输入的查询,您可以使用存储在会话中的随机数添加评论。然后将会话随机值与show processlist 结果中的行匹配,这将允许仍然使用查询缓存并根据您的需要进行唯一匹配

尽管您的查询足够长以至于需要这样的事情,这表明您应该看看为什么查询那么慢。

【讨论】:

    【解决方案4】:

    这是我们在其中一个系统上使用的解决方案。有两个不同的组件(UI 和报表处理器)使用数据库进行通信。

    用户界面从用户那里收集报告参数,然后在报告表中创建一个新条目,状态为new。报告生成不会自动开始。

    UI 列出了所有报告及其当前状态(newwaitingrunningkilledcompletedfailed)。当客户对报告参数感到满意时,他们会从报告列表中选择它并按下“开始报告”按钮,将报告状态从 new 更改为 waiting

    每分钟在服务器上运行的 cron 作业选择处于 waiting 状态的第一个报告,将其状态更改为 running 并开始从数据库中收集其数据。完成后,它会将状态更改为completed(或failed,如果出现问题)并使用操作结果(失败时的错误消息)更新注释字段。

    客户可以在 UI 中看到所有报告的当前状态。如果他们想停止正在运行的报告,可以使用“Kill”按钮,UI 会将数据库中的报告状态更改为 killed

    报告生成涉及多个阶段和对数据库的大量请求。在这些阶段之间,甚至在这些阶段期间,当一个阶段有许多步骤时,脚本会检查它处理的报告的状态。虽然状态为running,但它会继续收集数据并撰写报告。如果状态更改为killed,它就知道用户改变了主意;它停止处理,将报告状态更改为failed,更新评论(为“被用户杀死”),清理并退出。

    这只是一个草图。有更多的进程状态,还有一些代码保留了 cron 作业的两个实例来处理相同的报告等等,但是这个模型为我们工作了四年多,没有出现重大问题。

    关于运行时间,小型报告需要几秒钟才能完成,但大型报告有时需要 2-3 小时。虽然可以让 5-10 秒的报告完成并在之后删除,但尽早终止不需要的报告,否则需要几个小时才能完成,这证明了实施复杂机制的合理性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-28
      • 2013-11-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多