Как PDO выполняет запросы в режиме отключённой эмуляции?

Фанат

oncle terrible
Команда форума
Накатал тут в охотку постик на стаковерфлое.
Блин, там таких одекватных, кто по делу пишет, по пальцам одной ноги пересчитать можно. Я да Зер. А оне меня банют.
Ну да не суть.

Один вопрос остался невыясненным.
Судя по тестам, что query(), что prepare()/execute() выполняются строго одинаково по времени. При этом query() с включенной умуляцией получается быстрее той же квери с выключенной.
Объяснение этому факту у меня только одно - я правильно понимаю, что $pdo->query() всё равно делает приготовить/казнить в режиме отключённой эмуляции?
В исходники ломы лезть, вдруг кто сразу знает? :)
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Объяснение этому факту у меня только одно - я правильно понимаю, что $pdo->query() всё равно делает приготовить/казнить в режиме отключённой эмуляции?
я не смог понять это предложение :)

mysql обрабатывает плейсхолдеры медленно, поэтому PDO их обрабатывает само. Можно заставить PDO не помогать mysql, тогда на сервер пойдут запросы с плейсхолдерами. В чем вопрос?
 

Фанат

oncle terrible
Команда форума
у пдо есть два варианта отправить запрос на сервер -
query() - без препарации
и prepare()/execute() - соответственно, с.

Но исходя из того факта, что по времени оба варианта оказываются одинаковыми, я предполагаю, что первый всё равно внутри себя использует второй.

речь о случае, когда PDO не помогает мускулю.
 

fixxxer

К.О.
Партнер клуба
В вопросе о том, дергается ли prepare-execute или exec_query - похоже, зависит все не от опций pdo, а от параметров сборки.
Видимо, prepare-execute используется и при эмуляции.
PHP:
static int pdo_mysql_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
{
    pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
    pdo_mysql_db_handle *H = S->H;
    PDO_DBG_ENTER("pdo_mysql_stmt_execute");
    PDO_DBG_INF_FMT("stmt=%p", S->stmt);

#if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
    if (S->stmt) {
        PDO_DBG_RETURN(pdo_mysql_stmt_execute_prepared(stmt));
    }
#endif
    
    /* ensure that we free any previous unfetched results */
    if (S->result) {
        mysql_free_result(S->result);
        S->result = NULL;
    }

    if (mysql_real_query(H->server, stmt->active_query_string, stmt->active_query_stringlen) != 0) {
        pdo_mysql_error_stmt(stmt);
        PDO_DBG_RETURN(0);
    }

    PDO_DBG_RETURN(pdo_mysql_fill_stmt_from_result(stmt TSRMLS_CC));
}
/* }}} */
Касаемо эмуляции надо грепать по emulate_prepare, пока не очень ясно, что там происходит, какая-то жесть, надо вникать =)

Проще всего смотреть, что реально происходит, если проставить #define PDO_DBG_ENABLED 1.
 

fixxxer

К.О.
Партнер клуба
есть вот такой код

PHP:
/* {{{ mysql_handle_preparer */
static int mysql_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC)
{   
    pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
    pdo_mysql_stmt *S = ecalloc(1, sizeof(pdo_mysql_stmt));
#if defined(HAVE_MYSQL_STMT_PREPARE) || defined(PDO_USE_MYSQLND)
    char *nsql = NULL;
    int nsql_len = 0;
    int ret;
    int server_version;
#endif 
    
    PDO_DBG_ENTER("mysql_handle_preparer");
    PDO_DBG_INF_FMT("dbh=%p", dbh);
    PDO_DBG_INF_FMT("sql=%.*s", sql_len, sql);
    
    S->H = H;
    stmt->driver_data = S;
    stmt->methods = &mysql_stmt_methods;
    
    if (H->emulate_prepare) {
        goto end;
    }

#if defined(HAVE_MYSQL_STMT_PREPARE) || defined(PDO_USE_MYSQLND)
    server_version = mysql_get_server_version(H->server);
    if (server_version < 40100) {
        goto fallback;
    }   
    stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
    ret = pdo_parse_params(stmt, (char*)sql, sql_len, &nsql, &nsql_len TSRMLS_CC);
        
    if (ret == 1) {
        /* query was rewritten */
        sql = nsql;
        sql_len = nsql_len;
    } else if (ret == -1) {
        /* failed to parse */
        strcpy(dbh->error_code, stmt->error_code);
        PDO_DBG_RETURN(0);
    }

    if (!(S->stmt = mysql_stmt_init(H->server))) {
        pdo_mysql_error(dbh);
        if (nsql) {
            efree(nsql);
        }
        PDO_DBG_RETURN(0);
    }

    if (mysql_stmt_prepare(S->stmt, sql, sql_len)) {
        /* TODO: might need to pull statement specific info here? */
        /* if the query isn't supported by the protocol, fallback to emulation */
        if (mysql_errno(H->server) == 1295) {
            if (nsql) {
                efree(nsql);
            }
            goto fallback;
        }
        pdo_mysql_error(dbh);
        if (nsql) {
            efree(nsql);
        }
        PDO_DBG_RETURN(0);
    }
    if (nsql) {
        efree(nsql);
    }

    S->num_params = mysql_stmt_param_count(S->stmt);

    if (S->num_params) {
        S->params_given = 0;
#ifdef PDO_USE_MYSQLND
        S->params = NULL;
#else
        S->params = ecalloc(S->num_params, sizeof(MYSQL_BIND));
        S->in_null = ecalloc(S->num_params, sizeof(my_bool));
        S->in_length = ecalloc(S->num_params, sizeof(unsigned long));
#endif
    }
    dbh->alloc_own_columns = 1;

    S->max_length = pdo_attr_lval(driver_options, PDO_ATTR_MAX_COLUMN_LEN, 0 TSRMLS_CC);

    PDO_DBG_RETURN(1);

fallback:
#endif
end:
    stmt->supports_placeholders = PDO_PLACEHOLDER_NONE;

    PDO_DBG_RETURN(1);
}
/* }}} */
Тем есть mysql_stmt_prepare, ага. Если emulate_prepares выключен.
Если emulate_prepare включен, оно не вызывается. Но как работает эмуляция я чо-то пока не понял. :)
 

fixxxer

К.О.
Партнер клуба
Ага, а вот код собственно выполняющий запрос (в случае libmysql с поддержкой prepared)
PHP:
#ifdef HAVE_MYSQL_STMT_PREPARE
static int pdo_mysql_stmt_execute_prepared_libmysql(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
{
    pdo_mysql_stmt *S = stmt->driver_data;
    pdo_mysql_db_handle *H = S->H;

    PDO_DBG_ENTER("pdo_mysql_stmt_execute_prepared_libmysql");

    /* (re)bind the parameters */
    if (mysql_stmt_bind_param(S->stmt, S->params) || mysql_stmt_execute(S->stmt)) {
        if (S->params) {
            memset(S->params, 0, S->num_params * sizeof(MYSQL_BIND));
        }
        pdo_mysql_error_stmt(stmt);
        if (mysql_stmt_errno(S->stmt) == 2057) {
            /* CR_NEW_STMT_METADATA makes the statement unusable */
            S->stmt = NULL;
        }
        PDO_DBG_RETURN(0);
    }
    // дальше обработка результата
Вощем я нифига не понимаю как работает emulate :)
 

Фанат

oncle terrible
Команда форума
Хм. Я что-то тоже перестал.
Всегда был уверен, что libmysql не поддерживает prepared.
Ладно, не буду лезть в это болото, а то ещё засосёт :)
 

fixxxer

К.О.
Партнер клуба
Не, оно не поддерживало в старых версиях, потому все и обвешано ifdef-ами.

Но я наверное покопаюсь. По другой причине.
Мне недавно в голову приходила мысль, что можно сделать комбинированое решение: выключить emulate, из входного шаблона с кастомными навороченными плейсхолдерами генерировать в итоге подходящий для prepare запрос со сгенерированными плейсхолдерами, приводить все входящие параметры запроса к PDO-типам (PDO:: PARAM_*), и делать честный prepare-execute, тем самым избегая вообще необходимости париться на тему правильной обработки интов и строк (естественно, там, где это возможно - имена полей и тп это отдельный случай). Выходит, это не имеет смысла по причинам производительности?

И как с этим, кстати, в mysqli? Там вроде как эмуляции нет вообще:
http://php.net/manual/en/mysqli.quickstart.prepared-statements.php
The API does not include emulation for client-side prepared statement emulation.
 

Фанат

oncle terrible
Команда форума
Ну, ну то, что это неэффективно с точки зрения производительности - это ж было ясно изначально. "Два раунд-трипа - не один раунд-трип", как говорят преферансисты.
в mysqli нет никакой эмуляции - это ж честный API над mysqlnd, а не wannabe DAL системы " сейчас мы попробуем со всей этой херней взлететь".

имхо, препареды - мертвая идея. люди просто путают гениальную вещь - плейсхолдеры, с довольно убогой их реализацией - prepared statements.
 

fixxxer

К.О.
Партнер клуба
Вопрос в том, *насколько* неэффективно. :)

Все же в prepared-ах, честных, серверных, есть своя прелесть.
Когда на сервер отдельно идет шаблон запроса и отдельно его параметры - это исключает в принципе sql-инъекции и любые неочевидности с обработкой данных на клиенте: серверу сказали, что "вот эта хрень - это инт" - а там он сам разберется, что с ним делать. Другое дело, что возможности имеющихся шаблонов слишком малы для реального использования, их просто не хватает.
 

Фанат

oncle terrible
Команда форума
Все же в prepared-ах, честных, серверных, есть своя прелесть.
Дык, кто ж спорит.
Я и сам всегда раз поагитировать за идею разделения кода и данных - просто в силу прекрасности самой идеи.
С другой стороны, вроде бы, никаких проблем с обработкой (грамотной) параметров на клиенте тоже не наблюдается, неоднозначности разрешимы.
и по факту ручные плейсхолдеры получаются сильно удобнее готовых препаредов
 

WMix

герр M:)ller
Партнер клуба
fixxxer
научи читать сорцы пхп, я гляжу на то что ты накидал, следить вроде за мыслью могу, а так всекарно китайская грамота... структуры абстрактные, и еслиб не рассказывал о чем это, врятлиб догадалсяб... подскажи с чего начать
 

fixxxer

К.О.
Партнер клуба
отсюда :)

потом - http://stackoverflow.com/questions/4389738/where-can-i-learn-php-internals-how-they-work

хотя я когда именно читаю чтобы "понять как работает в целом", я смотрю "с высоты птичьего полета", не вникая в детали. Тут в принципе пофигу на каком языке написано, если это не brainfuck и писали не полные индусы. Это скорее навык, который приобретается с опытом программирования на куче разных языков.
 

Фанат

oncle terrible
Команда форума
Что характерно - тамошние вахтёры прикрыли топик.
А потом кто-то мне будет рассказывать, что стаковерфлой - богом данное благословение.
 

WMix

герр M:)ller
Партнер клуба
си я понимаю у меня ровно такая же книжка, причем первый раз я читал ее на немецком, писал всякую ерунду, иначеб не понял бы то что ты рассказывал...
PHP:
pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
pdo_mysql_stmt *S = ecalloc(1, sizeof(pdo_mysql_stmt));
тут вижу 2 поинтера на структуры, в первом случае мы его тянам из дбх во втором мы алоциилуем память (хотя это я так думаю, привык malloc писать)..
интересно видеть стуктуру pdo_mysql_db_handle, хотя предполагаю что это еще одна обертка для парочки других структур, нужен подход от простого к абстрактному...

вторую ссылку полистал еще раз поглядел на абстракцию counter basic и extended, далее создание Extensions, этим все никак время не находил заняться, но наверняка оттуда и нужно начинать... хотя скачал книжку http://www.amazon.com/dp/067232704X/?tag=stackoverfl08-20 в надежде понять основы, без попыток писать Extension, которые не пишу только потому что, не знаю что писать, и так все вроде уже есть, бери не хочу...
хочу просто понимать это https://github.com/php/php-src, откуда начинать читать (типа а где viod main( char[] args ))? что поглядеть сразу повнимательней...

ну да наверно хочу много не поднимая задницы....
 

fixxxer

К.О.
Партнер клуба
для того, чтобы понять общую логику работы этого экстеншена, ответы на эти вопросы совершенно не важны. :)
 

itprog

Cruftsman
Что характерно - тамошние вахтёры прикрыли топик.
А потом кто-то мне будет рассказывать, что стаковерфлой - богом данное благословение.
неадекватность есть везде, но рулит там всеравно community же, осталось 4 голоса набрать за reopen ;)
хотя с другой стороны, получился сборник ссылок, для которого достаточно было бы одного community-ответа
 

WMix

герр M:)ller
Партнер клуба
возможно ты прав, и читать это достаточно "с высоты птичьего полета",.. и если я хочу понимать действительно так как я это хочу нужно просто заставить себя! порою то и не задумываюсь как зенд работает и пишу пишу, возник вопрос побежал по сорцам и также на скоростях понимая половину в пойсках злочасного места прыгнул глазами туда сюда, представил что ожидают или где ошибка моего понимания, вернулся и переписал как нужно... имагик тот вообще изучал по названиям функций,... а порою умл говорит больше чем сам код...
 

Фанат

oncle terrible
Команда форума
неадекватность есть везде, но рулит там всеравно community же, осталось 4 голоса набрать за reopen ;)
Это-то понятно.
Ты лучше скажи, для чего тебе версия мускуля? :)
Если чо - я взял себе железное правило никогда не отвечать на комментарии. Надеюсь, это поможет мне избежать очередного бана.
Так что там не спрошу, но мне зело любопытно, какое отношение к вопросу может иметь версия мускуля?
 
Сверху