Убить запрос MySQL на прерывание пользователя

При запуске длинного запроса из PHP [как] я могу убить запрос, если пользователь нажимает кнопку остановки в своем браузере?

Учтите, что я не могу вызывать какие-либо другие функции PHP, потому что PHP блокируется, ожидая MySQL.

Также я не могу делать больше запросов на сервер (через Ajax) из-за блокировки сеанса.

Таким образом, одним из решений может быть:

  • игнорировать пользовательский прерывание
  • запустите длинный запрос в фоновом режиме и проверьте PHP каждые 100 мс, если он закончил.
  • получить pid из запроса
  • если пользователь прерывает, убивает pid
  • else возвращает результат при завершении

2 вещи, которые я не знаю, как это сделать:

  • запустить неблокирующий (фоновый) запрос
  • получить pid запроса

Ответ 1

Для тех, кто заинтересован, вот что я использовал:

<?php
// Connection to query on
$query_con = mysqli_connect($host, $user, $password, $name, $port);

// Connection to kill on
$kill_con = mysqli_connect($host, $user, $password, $name, $port);

// Start the query
$query_con->query($slow_query, MYSQLI_ASYNC);

// Get the PID
$thread_id = $query_con->thread_id;

// Ignore user abort so we can kill the query
ignore_user_abort(true);

do  {
    // Poll MySQL
    $links = $errors = $reject = array($mysqli->mysqli);
    $poll = mysqli_poll($links, $errors, $reject, 0, 500000);

    // Check if the connection is aborted and the query was killed
    if (connection_aborted() && mysqli_kill($kill_con, $thread_id)) {
        die();
    }
} while (!$poll);

// Not aborted, so do stuff with the result
$result = $link->reap_async_query();
if (is_object($result)) {
    // Select
    while ($row = $result->fetch_object()) {
        var_dump($row);
    }
} else {
    // Insert/update/delete
    var_dump($result);
}

Ответ 2

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

Другой способ, которым вы могли бы это сделать, - запустить script с ignore_user_abort() и проверить, если пользователь прерывается путем периодического вызова connection_aborted(). Если пользователь прервал, убейте запрос и изящно выйдите из script.

Подробнее о подключении здесь.

Ответ 3

Если время, затраченное на запрос, связано с возвратом большого набора данных, вы можете использовать mysql_unbuffered_query. А затем в shutdown_function вы можете освободить результат и отключить форму mysql.

И если вы используете PHP 5.3.x и имеете mysqlnd, вы можете использовать MYSQLI_ASYNC в mysqli_query, а затем использовать mysqli_poll для получения результата.

Первый вариант поможет только в случае времени, затраченного на извлечение набора данных. Но если mysql на самом деле занимает много времени в разборе и создании плана выполнения и загрузки таблиц в память, это не поможет. В этом случае вам нужно MYSQLI_ASYNC, которое доступно только на PHP 5.3.x WITH mysqlnd.

Также, если ваша главная проблема - session locking, посмотрите этот пост в документации PHP http://php.net/manual/en/ref.session.php#64525
Вы можете найти полезные советы.

Ответ 4

Я думаю, вы можете попробовать mysql KILL

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

если вы можете вызывать что-то по поводу прерывания пользователя, вы можете использовать KILL, чтобы убить соединение или запрос mysql, извлекая его идентификатор процесса.

И как вы сказали в своем комментарии, что PHP блокируется до тех пор, пока mysql не вернет

поэтому вы можете воспользоваться помощью javascript таким образом:

1.позволить пользователю прервать использование JS 2. вызвать функцию прерывания пользователя 3. В этой функции вы можете убить предыдущий запрос, но здесь вам нужен идентификатор процесса предыдущего запроса, поэтому вы можете получить это с помощью трюка, что вы можете выбрать идентификатор процесса при запуске первого запроса и передать его функции JS и использовать его при убийстве вашего запроса.

Надеюсь, это поможет вам.

Ответ 5

PHP, в большинстве случаев, не будет знать, что соединение было закрыто (поскольку пользователь был прерван), пока он не попытается отправить что-то по сети, а затем получит SIGPIPE. Единственное, что вы можете сделать, это попытаться работать с тайм-аутами, прерывая длинные запросы или убивая их из другого процесса.

Ответ 6

Это пример того, как это делается с PDO.

$id = $connection->executeQuery("SELECT CONNECTION_ID()")->fetchColumn();
ignore_user_abort(true);
while($row = $result->fetch()) {
  /* Processing ... */
  if (connection_aborted()) {
    $this->connection->executeQuery("KILL CONNECTION $id");
    break;
  }
}

Примечание: следите за несколькими подключениями, вы можете отключить неправильное.