- PVSM.RU - https://www.pvsm.ru -

Асинхронные запросы к MySQL

В mysqlnd [1] появилась возможность выполнять запросы к 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 [2]

Выполнение асинхронных запросов

Для выполнения асинхронных запросов достаточно указать специальный флаг 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 [3]. К сожалению, функция не документирована, а набор параметров вызывает недоумение, поэтому мне пришлось лезть в исходники и смотреть что же все-таки происходит. В итоге выяснилось что функция является оберткой для системного вызова select [4], наподобие функции stream_select [5]:

image

На вход функции mysqli_poll подаются три массива, содержащие объекты mysqli, которые необходимо проверить и значения таймаута проверки (sec, [usec]). Массивы преобразуются в соответствующий набор файловых дескрипторов, значения для таймаута преобразуются в структуру timeval и отдаются на вход системному вызову select, затем совершается обратное преобразование дескрипторов в объекты mysqli и возвращается результат системного вызова select. Примеры проверки результатов асинхронных запросов можно посмотреть здесь [6].

Получение результатов запроса

Получение результатов выполнения асинхронного запроса происходит через функцию mysqli_reap_async_query [7]. Вызов функции необходим всегда, так как он снимает блокировку с запроса и без этого вызова, все последующие запросы будут падать с «Commands out of sync».Для запросов на чтение (SELECT и тд), функция вернет mysqli_result, который после обработки необходимо «освободить» для продолжения работы с БД, для остальных — bool.

Варианты использования

Основным преимуществом использования асинхронных запросов является более эффективное использование процессорного времени. Вначале статьи есть пример, который показывает насколько быстро можно выполнять множества асинхронных запросов к БД (код [8]). Кроме этого перед разработчиком достаточно часто встает задача написания миграций в БД. Миграции, как правило, делятся на миграции схемы(CREATE, ALTER, DROP) и миграции данных (INSERT, UPDATE, DELETE). В случае работы с большими таблицами, выполнение ALTER занимает большое кол-во времени, которое можно эффективно использовать для подготовки данных для UPDATE (код [9]).

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


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/mysql/17778

Ссылки в тексте:

[1] mysqlnd: http://habrahabr.ru/post/154663/

[2] github: https://github.com/aeryaguzov/php-mysqlnd-examples

[3] mysqli_poll: http://php.net/manual/ru/mysqli.poll.php

[4] select: http://www.kernel.org/doc/man-pages/online/pages/man2/select.2.html

[5] stream_select: http://php.net/manual/ru/function.stream-select.php

[6] здесь: https://github.com/aeryaguzov/php-mysqlnd-examples/blob/master/polling.php

[7] mysqli_reap_async_query: http://www.php.net/manual/ru/mysqli.reap-async-query.php

[8] код: https://github.com/aeryaguzov/php-mysqlnd-examples/blob/master/sync_vs_async_time.php

[9] код: https://github.com/aeryaguzov/php-mysqlnd-examples/blob/master/mersenne_migration.php