В mysqlnd появилась возможность выполнять запросы к MySQL асинхронно, то есть продолжить работу скрипта не дожидаясь выполнения запроса и формирования результата. Преимущество такого подхода очевидно, ведь можно выполнить массу полезной работы во время ожидания запроса, но для начала я приведу немного другой пример:
Допустим у Вас есть 3 запроса (q1, q2, q3), каждый запрос выполняется за определенное время (t1, t2, t3), например так:
SELECT 1 AS val, SLEEP(1) AS sleep
SELECT 2 AS val, SLEEP(2) AS sleep
SELECT 3 AS val, SLEEP(3) AS sleep
В случае синхронного выполнения запросов, Вы сможете получить результаты их выполнения через t1 + t2 + t3 (ex: 6 секунд), а в случае асинхронного выполнения запросов уже за max(t1, t2, t3) (ex: 3 секунды)
Примеры работы с асинхронными запросами, а также другие примеры работы с mysqlnd можно найти на github
Выполнение асинхронных запросов
Для выполнения асинхронных запросов достаточно указать специальный флаг MYSQLI_ASYNC.
mysqli_query($link, $query, MYSQLI_ASYNC);
Данная константа объявляется непосредственно в расширении, поэтому выражения выше не выполнится без mysqlnd. Для обеспечения возможности выполнить запрос синхронно, в случае отсутствия mysqlnd, существует несколько вариантов выполнения запроса:
// часто встречал именно такой вариант(будет notice на не объявленную константу - нехорошо!)
mysqli_query($link, $query, MYSQLI_ASYNC || MYSQLI_USE_RESULT);
// другой вариант
$flag = defined('MYSQLI_ASYNC') ? MYSQLI_ASYNC : MYSQLI_USE_RESULT;
mysqli_query($link, $query, $flag);
// третий вариант
defined('MYSQLI_ASYNC') || define('MYSQLI_ASYNC', MYSQLI_USE_RESULT);
mysqli_query($link, $query, MYSQLI_ASYNC);
В отличии от обычного результата работы этой функции, в случае асинхронного выполнения запроса на чтение (SELECT и тд) она вернет true, вместо mysqli_result.
Проверка выполнения асинхронных запросов
Для проверки выполнения асинхронных запросов используется функция mysqli_poll. К сожалению, функция не документирована, а набор параметров вызывает недоумение, поэтому мне пришлось лезть в исходники и смотреть что же все-таки происходит. В итоге выяснилось что функция является оберткой для системного вызова select, наподобие функции stream_select:
На вход функции mysqli_poll подаются три массива, содержащие объекты mysqli, которые необходимо проверить и значения таймаута проверки (sec, [usec]). Массивы преобразуются в соответствующий набор файловых дескрипторов, значения для таймаута преобразуются в структуру timeval и отдаются на вход системному вызову select, затем совершается обратное преобразование дескрипторов в объекты mysqli и возвращается результат системного вызова select. Примеры проверки результатов асинхронных запросов можно посмотреть здесь.
Получение результатов запроса
Получение результатов выполнения асинхронного запроса происходит через функцию mysqli_reap_async_query. Вызов функции необходим всегда, так как он снимает блокировку с запроса и без этого вызова, все последующие запросы будут падать с «Commands out of sync».Для запросов на чтение (SELECT и тд), функция вернет mysqli_result, который после обработки необходимо «освободить» для продолжения работы с БД, для остальных — bool.
Варианты использования
Основным преимуществом использования асинхронных запросов является более эффективное использование процессорного времени. Вначале статьи есть пример, который показывает насколько быстро можно выполнять множества асинхронных запросов к БД (код). Кроме этого перед разработчиком достаточно часто встает задача написания миграций в БД. Миграции, как правило, делятся на миграции схемы(CREATE, ALTER, DROP) и миграции данных (INSERT, UPDATE, DELETE). В случае работы с большими таблицами, выполнение ALTER занимает большое кол-во времени, которое можно эффективно использовать для подготовки данных для UPDATE (код).
P.S. Работа с асинхронными запросами пока не полностью прозрачна для меня, поэтому я прошу Вашей помощи:
1. Я так и не смог сэмулировать получение ошибок(появления объекта mysqli в массиве errors) через mysqli_poll. Если Вы знаете как это сделать, напишите плз, обязательно добавлю в статью.
2. Если закрыть соединение перед вызовом mysqli_poll у меня получается segfault(ubuntu 12.04, php 5.3.10). Воспроизведите плз у себя следующий код, возможно надо репортить баг:
$link = new mysqli('host', 'user', 'password', 'db', 'port');
mysqli_close($link);
$read = $error = $reject = array();
$read[] = $error[] = $reject[] = $link;
mysqli_poll($read, $error, $reject, 1);
Автор: aeryaguzov