Если вы используете собственный драйвер mysql (обычно начиная с php 5.3) и расширение mysqli , это можно сделать с помощью асинхронного запроса:
<?php
// Here's an example query that will take a long time to execute.
$sql = "
select *
from information_schema.tables t1
join information_schema.tables t2
join information_schema.tables t3
join information_schema.tables t4
join information_schema.tables t5
join information_schema.tables t6
join information_schema.tables t7
join information_schema.tables t8
";
$mysqli = mysqli_connect('localhost', 'root', '');
$mysqli->query($sql, MYSQLI_ASYNC | MYSQLI_USE_RESULT);
$links = $errors = $reject = [];
$links[] = $mysqli;
// wait up to 1.5 seconds
$seconds = 1;
$microseconds = 500000;
$timeStart = microtime(true);
if (mysqli_poll($links, $errors, $reject, $seconds, $microseconds) > 0) {
echo "query finished executing. now we start fetching the data rows over the network...\n";
$result = $mysqli->reap_async_query();
if ($result) {
while ($row = $result->fetch_row()) {
// print_r($row);
if (microtime(true) - $timeStart > 1.5) {
// we exceeded our time limit in the middle of fetching our result set.
echo "timed out while fetching results\n";
var_dump($mysqli->close());
break;
}
}
}
} else {
echo "timed out while waiting for query to execute\n";
var_dump($mysqli->close());
}
Флаги, которые я даю mysqli_query , выполняют важные задачи. Он говорит клиентскому драйверу включить асинхронный режим, в то же время заставляя нас использовать более подробный код, но позволяет нам использовать тайм-аут (а также выдавать параллельные запросы, если хотите!). Другой флаг говорит клиенту не буферизовать весь набор результатов в память.
По умолчанию php конфигурирует свои клиентские библиотеки mysql для извлечения всего результирующего набора вашего запроса в память, прежде чем он позволит вашему php-коду начать получать доступ к строкам в результате. Это может занять много времени для передачи большого результата. Мы отключаем его, в противном случае мы рискуем оказаться в тайм-ауте в ожидании завершения буферизации.
Обратите внимание, что есть два места, где нам нужно проверять превышение лимита времени:
- Фактическое выполнение запроса
- при получении результатов (данных)
Вы можете сделать подобное в PDO и обычном расширении mysql. Они не поддерживают асинхронные запросы, поэтому вы не можете установить тайм-аут на время выполнения запроса. Однако они поддерживают небуферизованные результирующие наборы, и поэтому вы можете по крайней мере реализовать тайм-аут при получении данных.
Для многих запросов mysql может начать потоковую передачу результатов вам практически сразу, и поэтому одни только небуферизованные запросы позволят вам несколько эффективнее реализовать тайм-ауты для определенных запросов. Например,
select * from tbl_with_1billion_rows
может сразу начать потоковую передачу, но,
select sum(foo) from tbl_with_1billion_rows
необходимо обработать всю таблицу, прежде чем она сможет начать возвращать вам первую строку. Это последний случай, когда время ожидания асинхронного запроса спасет вас. Это также спасет вас от старых тупиков и прочего.
ps - я не включил никакой логики тайм-аута в само соединение.