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

    В 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:

    image

    На вход функции 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);
    


    UPD: Баг открыт, quickfix уже есть

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 16

      +7
      В случае синхронного выполнения запросов, Вы сможете получить результаты их выполнения через t1 + t2 + t3 (ex: 6 секунд), а в случае асинхронного выполнения запросов уже за max(t1, t2, t3) (ex: 3 секунды)

      Конечно, т.к. в приведенном случае они (запросы) ничего не делают. А для реально выполняющихся запросов понадобится все 6 секунд плюс время на переключения потоков (в случае, если у нас одно ядро)…
        +1
        Ох уж эта Ваша реальность:-)

        Согласен с тем, что пример не реален, а идеален. Но считаю, что именно от этого надо плясать, поэтому и придумал именно такой пример. Насчет цифр — есть пример на гибхабе, туда вставляйте свой запрос и смотрите, для каждого цифры будут разные, например, у меня асинхронный вариант, все равно(но не всегда) выполняется немного быстрее
        –5
        Т.е я могу во время загрузки страницы кинуть AJAX-запрос и асинхронно сделать асинхронные запросы и всё будет супер быстро?

        Как я понял, они буду выполняться одновременно, а не последовательно?
          0
          В целом такой вариант использования скорее анти-паттерн и использовать этот функционал таким образом — нехорошо, потому что надо дождаться результата выполнения запроса, а не оставлять его на произвол судьбы. Однако основная мысль именно такая: если в скрипте присутствует только выполнение запроса, то да, все будет очень быстро, но потом новый запрос в том же соединении не заработает(Commands out of sync) пока не будут получены результаты текущего, то есть надо будет сотворить что-то в стиле: выполнил запрос асинхронно, закрыл соединение. Остается вопрос: что будет если закрыть соединение у выполняющегося асинхронно апдейта например? Думаю, что ничего хорошего, а оставлять соединение открытым вообще плохо.

          Если Вы про пример, то одновременно, так как разные соединения.
            +4
            для этого не нужен mysqlnd
              0
              Автор имел ввиду запускать запрос из основного скрипта, а аяксом подтягивать резултат.
              Думаю на высоко нагруженных ресурсах очередь быстро переполнится.
                0
                Это… странный способ. Если запрос тяжелый, можно кешироватью
            0
            Рабочие примеры есть?
              +2
              Учитывая то, что оно требует отдельное подключение к MySQL для каждого параллельно выполняющегося запроса вместо пайплайнинга, выглядит абсолютно бесполезно в 99% случаев.
              Может пригодиться только если вы пишете на PHP демонов и держите пул подключений.
                0
                Подключение к MySQL выполняется весьма быстро, порядка 1 мс, но для обычной веб-страницы это всё равно изврат, конечно :)).
                  0
                  Есть у вас импорт например… Построчный… Лучше же делать в 20 потоков чем в 1, не?
                    0
                    Это может отлично пригодится если в проекте используете шардирование данных на уровне бизнес-логики и возникает необходимость сделать 10 одинаковых запросов к 10 разным серверам.
                    0
                    А на примере PDO показать не лучше было? Или поддержка только в mysqli?
                      0
                      При использовании mysqlnd доп. функции появляются только в mysqli
                        0
                        спасибо, значит бесполезно пока для моих проектов, уж слишком много вкусного у mysqli нету.
                          +1
                          Я бы сказал наоборот, уж слишком много фич из mysqli в PDO нет…

                    Only users with full accounts can post comments. Log in, please.