Асинхронные запросы к MySQL на API (libmysqlclient)

    Так получается, что сейчас тружусь над планировщиком для MySQL соединений. И тут недавно пришлось покапаться в документации/блогах и т.д. И вот решил поделиться с сообществом как реализовать асинхронные запросы к MySQL серверу на С++ используя API и библиотеку libmysqlclient.



    Для того, чтобы начать программировать с использованием API MySQL, нам нужен заголовочный файл mysql.h.
    И библиотека libmysqlclient. Для deb-подобных ОС всё это можно поставить так:
    sudo apt-get install libmysqlclient-dev libmysqlclient
    


    Компилятору указываем следующее:
    -L/path/to/mysqliclientlib/ -lmysqlclient

    Если пишите код в Qt Creator, то в файле проекта можно добавить следующее(у вас может быть другой путь):
    LIBS += -L/usr/lib/mysql -lmysqlclient

    У нас почти всё готово, для того чтобы работать с MySQL на С++.
    Определяемся: асинхронные запросы — несколько параллельных запросов к базе без использования потоков (thread). Используя функцию mysql_real_query этого добиться невозможно, т.е. программа будет ожидать результат(ответ) от сервера.

    Кроме всего прочего, я использую библиотеку libevent для отлова событий. Поставить её можно так:
    sudo apt-get install libevent-dev libevent
    


    Указываем компилятору:
    LIBS += -L/usr/lib/mysql -lmysqlclient -L/usr/lib/ -levent
    Заголовочный файл:
    #include <event.h>

    Всё, всё готово. Приступаем. Итак пишем класс который будет посылать запросы асинхронно. У меня этот класс содержит ещё методы — я остановлюсь только на тех, которые имеют отошение к теме.

    class CBalanceMySQL
    {
        private:
            // ваши приватные члены
    
            // число одновременных процессов 
            unsigned int thread_count;
    
            // соединения с базой
            std::vector<MYSQL*> v;
            // события
            std::vector<event*> events;
            // запросы
            std::vector<std::string> queries;
    
            struct event_base *evbase;
    
            // указатель на функцию, при успешном выполнении запросы и 
            // ответа от сервера БД
            pFunc p_func;
    
            // метод для выполнения запросов асинхронно
            void run();
        public:
            // constructor
            CBalanceMySQL();
    
            // destructor
            ~CBalanceMySQL();
    
            // Тут ваши остальные публичные методы
    
            // устанавливаем число процессов одновременных
            void setThreadCount(int);
    
            // соединяемся с базой
            bool ConnectAllThreads();
    
            // даём указатель на функцию в случае успеха
            bool setSuccessFunc(void(*)(int, short, void*));
    
    };
    


    В конструкторе класса (или где-то ещё) инициализируем событийную базу:
    this->evbase = event_base_new();
    


    Далее нам будет необходимо установить соединения с сервером и «запомнить» их. Бежим в цикле по заданном числу запросов и сохраняем соединения в векторе v.
    bool CBalanceMySQL::ConnectAllThreads() {
        for (uint i = 0; i < this->thread_count; i++) {
            MYSQL *m;
            m = mysql_init(NULL);
            if (!mysql_real_connect(m, this->host, this->user, this->password, NULL, this->port, NULL, 0)) {
                std::cout << mysql_error(m);
                return 0;
            } else {
                this->v.push_back(m);
            }
        }
     return 1;
    }
    


    Осталось выполнить нужные нам запросы по этим коннекшнам:
    void CBalanceMySQL::run() {
    for (uint i = 0; i < this->v.size(); i++) { 
                // событие
                struct event *ev;
                // запрос к конкретному коннекту
                std::string q = this->queries.at(i);
                // ныжный нам коннект
                MYSQL *m = this->v.at(i);
                // выполняем запрос
                mysql_send_query(m, q.c_str() , strlen(q.c_str()));
                ev = new event;
                // регистрируем событие
                // по ответу - вызывается функция по указанному адресу p_func
                event_set(ev, m->net.fd, EV_READ, this->p_func, m);
                event_base_set(this->evbase, ev);
                event_add(ev, NULL);
                events.push_back(ev); 
            }
             // цикл до тех пор пока события не зарегистрируются  
             while (0 == event_base_loop(this->evbase, 0));         
    } 
    


    На этом почти всё. Осталось почистить за собой:

    for (uint i = 0; i < v.size(); i++) {
            mysql_close(this->v.at(i));
    }
        for (uint i = 0; i < events.size(); i++) {
            delete(this->events.at(i));
        }
        event_base_free(this->evbase);
    


    И последнее — функция обработки результата запроса. Она имеет вид:

    static void read_result(int fd, short event, void *_userdata) {
        MYSQL *m = (MYSQL*)_userdata;
        if (0 == mysql_read_query_result(m)) {
            MYSQL_RES *res = mysql_store_result(m);
            MYSQL_ROW row = mysql_fetch_row(res);
            // у меня был запрос  SELECT COUNT(*) FROM information_schema.processlist, я просто вывожу число соединений
            cout << "cnt for net.fd = " << fd << " is " << row[0] << endl;
            mysql_free_result(res);
        }
    }
    


    На этом всё. Надеюсь кому-то пригодится.
    p.s. сильно не пинать — в C++ не гуру.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 46

      +3
      Ох опять эта гребаная венгерская нотация…
        +1
        Ох опять эти гребаные египетские скобки…

        p.s. не пинаю, просто наболело)
          –1
          А где вы её увидели?
            +1
            Честно говоря сам не понял :) может имеласт в виду вот эта строчка?
            pFunc p_func;
            
          0
          p.s. сильно не пинать
            +5
            for (uint i = 0; i < v.size(); i++) {
            mysql_close(this->v.at(i));
            }

            Никогда так не перебирайте коллекции, никогда. Для доступа к элементам есть итераторы. Должно быть так:

            for(std::vector::iterator it = this->v.begin(); it != v.end(); ++v)
            delete *it;

            Ну и в остальных вариантах так же.
              0
              Хабр сожрал данные в угловых скобках
              std::vector<MYSQL*>::iterator
                +1
                Вот это важно. Спасибо, поправлю.
                0
                Векторы почему-то быстрее перебираются индексами.

                Кстати, у вас ошибка. должно быть ++it, да и this как бы не нужен.
                  0
                  И если нетрудно — объясните почему так нужно перебирать? Ведь циклом я определяю лишь число итераций, а для доступа к элементу вектора использую метод at() — где здесь потенциальная ошибка?
                    +1
                    Ну одно из первых, это родная реализация библиотеки, в случае итераторов вы имеете возможность поменять контейнер, в вашем случае лучше бы подошел std::list, быстрая вставка и быстрый последовательный перебор.

                    Я бы посоветовал всегда объявлять свои типы с помощью typedef и придерживаться минимального необходимого набора операций, в данном случае at лежит в самом конец и реализован далеко не во всех контейнерах, тогда как форвард итератор поддерживают все контейнеры.

                    Код, что выше писал ночью, что-то накосячил :-) как выше сказано ++it вместо ++v и почему-то delete вместо mysql_close использовал, мозг спал.
                      0
                      > и быстрый последовательный перебор.
                      перебор вектора как раз быстрее, так как процесору проще прекешировать данные из-за чего сокращается количество кеш мисов.
                      удалять из вектора можно за O(1) перемещая последний элемент на место удаляемого
                    0
                    А можно и:

                    for (auto it = v.begin(); it != v.end(); ++it)
                      delete *it;
                    


                    А еще лучше:
                    for_each (v.begin(), v.end(), [&](MYSQL* ptr){ if (ptr) delete ptr;});
                    v.clear();
                    


                    А если заюзать «сверхновый» range for (в VS2010 нет его):
                    for (auto it : v)
                    {
                      if (*it) delete *it;
                    }
                    

                      0
                      Можно, никто не запрещает. Но опять же if(*it) не требуется, ибо стандарт говорит о том, что delete 0, не вызовет ошибки, это допустимая операция, ничего не будет удалено, да и к примеру в boost давно это используется :-)
                        0
                        Хм… не знал — обязательно почитаю. Ну а если хранить shared_ptr на MYSQL, то можно и обойтись v.clear().
                          +1
                          но переопределенные операторы new/delete ничего не гарантируют.
                      0
                      > устанавливаем число процессов одновременных

                      Мастер Йода О_о?
                        0
                        Будьте осторожны, тут есть небольшая архитектурная ошибка. Если между ConnectAllThreads() и run() пройдет продолжительное время (в конфигурации MySQL по умолчанию — 28800 секунд) то запросы не выполнятся т.к. соединения разорвется по wait_timeout.
                          0
                          У меня такая же обертка для .Net, я это решаю 'select 1;'
                            0
                            Согласен. MySQL откинет «бездействующие» коннекты по умолчанию через 28800 секунд. Но в моём случае wait_timeout точно не пройдёт.
                            0
                            А чем вас так прельстила асинхронность на стороне MySQL?
                            В чем проблема работать с базой синхронно, но в отдельных потоках на клиенте?
                            Или тут больше интерес исследовательского характера?
                              0
                              Честно говоря пока, как вы написали интерес скорее «исследовательского» характера. Пробовал и многопоточно реализовывать (демон на PHP) — но вот сейчас решил попробовать таким путём пойти. Ну и в ресурсах естественно тут преимущество.
                                +1
                                Посмотрите gearMan — то что Вам нужно.
                                  0
                                  Спасибо. Покопаю.
                              0
                              интересная идея скрестить либэвент и либмускуль.
                              это можно было реализовать и на чистом си.
                                +1
                                Какая-то сомнительная асинхронность. Или вы утверждаете, что mysql_real_connect() и mysql_read_query_result() неблокирующие функции?
                                  –1
                                  А что какие объекты СУБД они блокируют по-вашему?
                                    +1
                                    СУБД тут не причем.

                                    Блокирующие функции в смысле блокирующие выполнение программы на неопределенный срок. Ведь идея асинхронности в том и состоит, чтобы заменить блокирующие операции неблокирующими. Пока потенциально долгая операция выполняется, программа занимается чем-то еще.

                                    Я подозреваю, что mysql_real_connect() внутри себя делает connect() на сокете. А для установления соединения нужно обменяться несколькими сообщениями с удаленным сервером. Сеть может лагать; connect может занять много времени и это не гипотетическая ситуация а вполне реальная.

                                    Это то что лежит на поверхности. А еще например если имя хоста надо ресолвить, то внутри mysql_real_connect() должен быть запрос к DNS, и врядли он выполняется асинхронно.
                                      0
                                      /*
                                      Send the query and return so we can do something else.
                                      Needs to be followed by mysql_read_query_result() when we want to
                                      finish processing it.
                                      */
                                      
                                      int STDCALL mysql_send_query(MYSQL* mysql, const char* query, ulong length)
                                      {
                                      DBUG_ENTER(“mysql_send_query”);
                                      DBUG_RETURN(simple_command(mysql, COM_QUERY, (uchar*) query, length, 1));
                                      }
                                      …
                                      my_bool STDCALL mysql_read_query_result(MYSQL *mysql)
                                      {
                                      return (*mysql->methods->read_query_result)(mysql);
                                      }
                                      
                                        +1
                                        Ну и что? Скопируйте уже весь код libmysqlclient, приведенного кода явно не достаточно.

                                        Disclaimer: я не особо знаком с кодом libmysqlclient. Честно говоря я туда вобще не заглядывал. А вот libev/libevent знаком хорошо. Сокет считается готовым для чтения как только оттуда можно прочитать хотя бы один байт. Соответственно ваш обработчик будет вызван еще до того, как ответ от mysql сервера будет передан полностью. Обработчик вызовет mysql_read_query_result(), которая заблокируется до тех пор, пока не получит тело ответа целиком.

                                        Ну и где здесь асинхронность, извините?
                                          0
                                          в общем у меня получилось скрестить pastebin.ru/nzhiAsEw libev и libmysql
                                          нужно написать статью что как и почему?
                                            0
                                            Есть подозрение, что все не очень хорошо:
                                            • mysql_connect() is blocking only
                                            • mysql_send_query() doesn’t seem to handle EAGAIN (send buffer is full)
                                            • I don’t know if mysql_use_result() can be return EAGAIN easily

                                              0
                                              да, согл — но это уже лучше чем ничего.
                                              mysql_use_result для асинхронного выполнения запросов не совсем подходит.

                                              мой коллега пошел по более простому пути,
                                              он переопределил функцию my_init() — т.е. определил свою с этим же именем и написал частично свой код, в которой реализовал неблокируемый mysql_connect() и реализовал асинхронность с помощью коротин (pcl).
                                                0
                                                >mysql_connect() is blocking only
                                                можно его переписать на неблокирующий режим,
                                                можно предварительно открыть не блокируемый сокет и самостоятельно, но к сожалению mysql_connect() — это больше чем просто коннект. Проще не трогать,
                                                да и вообще, всегда и даже в моем маленьком примере первоначально создается пул коннекций, а потом параллельно начинаем выполнять запросики.
                                                >I don’t know if mysql_use_result() can be return EAGAIN easily
                                                состояние EAGAIN можно отлавливать непосредственно в libev,
                                              0
                                              выдаю два запроса
                                              data[0].sql = «select sleep(3)»;
                                              data[1].sql = «SELECT `crc` FROM hidden_contacts LIMIT 5»;


                                              результат:
                                              $ ./test test.c:110 connected m->net.fd = 3
                                              test.c:110 connected m->net.fd = 4
                                              call timer cb…
                                              send query 'select sleep(3)'
                                              send query 'SELECT `crc` FROM hidden_contacts LIMIT 5'
                                              sent queryes
                                              start watcher id=1
                                              a result for m->net.fd = 4
                                              sql_p2_cb
                                              3272202
                                              6444339
                                              6877213
                                              7407359
                                              7672453
                                              free stor result id=1
                                              start watcher id=0
                                              a result for m->net.fd = 3
                                              sql_p1_cb
                                              query is Ok res=0
                                              free stor result id=0
                                      –1
                                      А что какие объекты СУБД они блокируют по-вашему?
                                        0
                                        Вообще есть mysqli и там уже всё это сделано. Можете почитать например тут php.net/manual/ru/mysqli.poll.php
                                          0
                                          Для c++?
                                            0
                                            Извиняюсь. Сглупил. Не сразу понял что автор пишет само приложение на c++. Думал изобретает модуль для php.
                                              0
                                              Сам я по заголовку подумал, что для nodejs сейчас будет статья. Жаль.
                                                0
                                                  0
                                                  Так я о Вашей библиотеке знаю. Но статье с примерами жизненного использования в том числе установку, описание я бы обрадовался.
                                                    0
                                                    Я надеюсь в течении пары недель закончить с высокоуровневой обёрткой и вплотную займусь документацией и пиаром :) К тому времени, скорее всего, будет уже поддержка prepared statements, а значит по сути всё API libmysqlclient будет покрыто.
                                          0
                                          Сколько же у Вас this. Вы, наверное, из питона пришли?
                                            0
                                            А что произойдет если в тот же самый коннект отправить новый запрос, не дождавшись ответа на предыдущего?
                                              0
                                              статья не плохая, но к сожалению 90% плагиат

                                              оригинал "Async MySQL Queries with C-API" September 09, 2008
                                              jan.kneschke.de/2008/9/9/async-mysql-queries-with-c-api/
                                              >Асинхронные запросы к MySQL на API (libmysqlclient) от 30 мая 2012

                                              чуть чуть код переделан на C++

                                              ищем 10 совпадений

                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                              Самое читаемое