PHP PDO: как повторная подготовка оператора влияет на производительность

Я пишу полупростой класс-оболочку базы данных и хочу иметь метод выборки, который работал бы автоматически: он должен подготавливать каждый отдельный оператор только в первый раз и просто связывать и выполнять запрос на последовательных звонки.

Думаю, главный вопрос заключается в следующем: Как работает повторная подготовка того же оператора MySql, сможет ли PDO волшебным образом распознать оператор (так что мне не нужно) и прервать операцию?

Если нет, я планирую добиться этого, создав уникальный ключ для каждого отдельного запроса и сохранив подготовленные операторы в частном массиве в объекте базы данных - под его уникальным ключом. Я планирую получить ключ массива одним из следующих способов (ни один из которых мне не нравится). В порядке предпочтения:

  • попросите программиста передать дополнительный, всегда один и тот же параметр при вызове метода - что-то вроде basename(__FILE__, ".php") . __LINE__ (этот метод будет работать, только если наш метод вызывается в цикле - в большинстве случаев эта функциональность необходима)
  • попросите программиста передать совершенно случайную строку (скорее всего, сгенерированную заранее) в качестве дополнительного параметра
  • использовать сам переданный запрос для генерации ключа - получение хэша запроса или что-то подобное
  • добиться того же, что и в первой пуле (выше), вызвав debug_backtrace

Есть ли у кого-нибудь подобный опыт? Хотя система, над которой я работаю, действительно заслуживает некоторого внимания к оптимизации (она довольно большая и растет с каждой неделей), возможно, я ни о чем не беспокоюсь, и в том, что я делаю, нет никакого выигрыша в производительности. я делаю?


person raveren    schedule 25.01.2010    source источник
comment
Я думаю, что размещение подготовленного дескриптора оператора в массиве с SQL в качестве ключа - единственный разумный способ. Я не вижу никакой пользы в других предложенных вами методах. Однако мне действительно интересно, делает ли PDO автоматически эту оптимизацию...   -  person Inshallah    schedule 25.01.2010
comment
Однако, если запрос длинный и вызывается тысячи раз, поиск такого ключа в массиве станет узким местом в собственной ИМО.   -  person raveren    schedule 25.01.2010
comment
Я не знаю. Я могу быть совершенно не прав. Я недостаточно знаю о внутреннем устройстве массива PHP и никогда не тестировал производительность на этом уровне. Между прочим, это хорошая идея — проверить производительность различных способов, прежде чем внедрять один из них.   -  person Inshallah    schedule 25.01.2010


Ответы (5)


Хорошо, так как я избивал методы ввода запросов для кеша, отличные от простого использования самой строки запроса, я сделал наивный тест. Ниже сравнивается использование простой строки запроса с первым созданием хэша md5:

$ php -v
$ PHP 5.3.0-3 with Suhosin-Patch (cli) (built: Aug 26 2009 08:01:52)
$ ...
$ php benchmark.php
$ PHP hashing: 0.19465494155884 [microtime]
$ MD5 hashing: 0.57781004905701 [microtime]
$ 799994

Код:

<?php
error_reporting(E_ALL);

$queries = array("SELECT",
                 "INSERT",
                 "UPDATE",
                 "DELETE",
                 );
$query_length = 256;
$num_queries  = 256;
$iter = 10000;

for ($i = 0; $i < $num_queries; $i++) {
    $q = implode('',
           array_map("chr",
             array_map("rand",
                       array_fill(0, $query_length, ord("a")),
                       array_fill(0, $query_length, ord("z")))));
    $queries[] = $q;
}

echo count($queries), "\n";

$cache = array();
$side_effect1 = 0;
$t = microtime(true);
for ($i = 0; $i < $iter; $i++) {
    foreach ($queries as $q) {
        if (!isset($cache[$q])) {
            $cache[$q] = $q;
        }
        else {
            $side_effect1++;
        }
    }
}
echo microtime(true) - $t, "\n";

$cache = array();
$side_effect2 = 0;
$t = microtime(true);
for ($i = 0; $i < $iter; $i++) {
    foreach ($queries as $q) {
        $md5 = md5($q);
        if (!isset($cache[$md5])) {
            $cache[$md5] = $q;
        }
        else {
            $side_effect2++;
        }
    }
}
echo microtime(true) - $t, "\n";

echo $side_effect1 + $side_effect2, "\n";
person Inshallah    schedule 25.01.2010
comment
Спасибо за бенчмарк, к сожалению, я не смог заставить его работать здесь, так как в $cache никогда не было никаких данных, а $q был только DELETEArray, но я буду исследовать это дальше, как только у меня будет возможность - person Morfildur; 25.01.2010
comment
@dbemerlin, да, извините. Теперь это должно работать. Я немного отредактировал код после копирования/вставки из emacs, внося некоторые ошибки в процесс :-). - person Inshallah; 25.01.2010
comment
Я протестировал его сейчас на основе вашего скрипта с этими изменениями: создайте 100 случайных строк, соединив 20 раз случайную строку из $ запросов. foreach из этих случайных строк выполняет цикл $iter раз и каждый раз проверяет, существует ли строка в $cache, если она не установлена. для md5: foreach из случайных строк создает хэш, а затем зацикливает $iter раз и делает то же самое. Результат: при $iter=1 хэш md5 медленнее на 0,00008 мс, при низких значениях $iter (100 и ниже) реальной разницы в производительности нет (‹0,1 с), с увеличением количества итераций md5 получает больше преимуществ. Итак: Зависит от варианта использования. - person Morfildur; 25.01.2010
comment
Я провел тест Instalah с различными значениями $length и $iter, и кажется, что кэширование PHP каждый раз выигрывает. Думаю, я приму его ответ, поскольку в конце концов он был прав, указав окончательное решение (использовать целые запросы как ключи в кеше) в комментариях к исходному вопросу. Когда я закончу класс-оболочку, я попытаюсь опубликовать дополнительные тесты, стоит ли вообще кэшировать операторы (хотя ничего не обещаю:) - person raveren; 25.01.2010
comment
@dbemerlin, спасибо за ваш отзыв. Я увеличил количество запросов до 256 с некоторыми очень длинными, но для меня MD5 постоянно проигрывал. - person Inshallah; 25.01.2010
comment
Я бы также рекомендовал проводить бенчмаркинг с md5-строками, так как они могут (или не могут) быть быстрее для вашего варианта использования, особенно если у вас есть возможность повторно использовать хэш (т.е. в цикле), и они могут (или не могут) ) будет быстрее, если у вас есть более 4 разных запросов в тесте inshallahs. - person Morfildur; 25.01.2010
comment
Вы, ребята, знаете, что ассоциативные массивы реализованы как /hashmaps/? Поскольку полученный хэш md5 затем снова хешируется с тем, что PHP использует внутри для поддержания своих хэш-карт, прирост производительности может быть только при предварительном использовании md5, если а) внутреннее хеширование PHP имеет более низкую производительность, чем открытая реализация md5 (включая все накладные расходы вызова открытой функции md5, выделения достаточного количества памяти в куче для результирующего хэша md5 и т. д.) и б) входные данные для md5 значительно больше, чем хэш md5, несмотря ни на что (потому что результирующий md5 будет снова хеширован). - person Daniel Baulig; 15.10.2010

MySQL (как и большинство СУБД) кэширует планы выполнения для подготовленных операторов, поэтому, если пользователь А создает план для:

SELECT * FROM some_table WHERE a_col=:v1 AND b_col=:v2

(где v1 и v2 являются переменными привязки), затем отправляет значения для интерполяции СУБД, затем пользователь B отправляет тот же запрос (но с другими значениями для интерполяции), СУБД не нужно повторно генерировать план. то есть именно СУБД находит соответствующий план, а не PDO.

Однако это означает, что для каждой операции с базой данных требуется по крайней мере 2 цикла обмена (1-й для представления запроса, второй для представления переменных связывания), в отличие от одного цикла для запроса с литеральными значениями, тогда это приводит к дополнительным сетевым затратам. . Также есть небольшие затраты на разыменование (и поддержку) кэша запросов/планов.

Ключевой вопрос заключается в том, превышают ли эти затраты затраты на составление плана в первую очередь.

Хотя (по моему опыту) использование подготовленных операторов с Oracle определенно дает выигрыш в производительности, я не уверен, что то же самое верно для MySQL, однако многое будет зависеть от структуры вашей базы данных и сложности запроса (или, точнее, сколько различных вариантов может найти оптимизатор для решения запроса).

Попробуйте измерить его самостоятельно (подсказка: вы можете установить порог медленного запроса равным 0 и написать некоторый код для преобразования литеральных значений обратно в анонимные представления для запросов, записанных в журналы).

person symcbean    schedule 25.01.2010
comment
-1 Как описано в разделе Кэширование подготовленных операторов и сохраненных программ: < i> Сервер поддерживает кэши для подготовленных операторов и сохраненных программ для каждого сеанса. Операторы, кэшированные для одного сеанса, недоступны для других сеансов. Когда сеанс завершается, сервер отбрасывает все операторы, кэшированные для него. Возможно, путаница возникла из-за того, что подготовленные операторы после раскрытия переменных кэшируются обычным способом, как описано в разделе Как работает кэш запросов? - person eggyal; 10.03.2014

Поверьте мне, я делал это до и после создания кеша подготовленных операторов, прирост производительности был очень заметным - см. этот вопрос: Подготовка операторов SQL с PDO.

Это был код, который я придумал, с кэшированными подготовленными операторами:

function DB($query)
{
    static $db = null;
    static $result = array();

    if (is_null($db) === true)
    {
        $db = new PDO('sqlite:' . $query, null, null, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING));
    }

    else if (is_a($db, 'PDO') === true)
    {
        $hash = md5($query);

        if (empty($result[$hash]) === true)
        {
            $result[$hash] = $db->prepare($query);
        }

        if (is_a($result[$hash], 'PDOStatement') === true)
        {
            if ($result[$hash]->execute(array_slice(func_get_args(), 1)) === true)
            {
                if (stripos($query, 'INSERT') === 0)
                {
                    return $db->lastInsertId();
                }

                else if (stripos($query, 'SELECT') === 0)
                {
                    return $result[$hash]->fetchAll(PDO::FETCH_ASSOC);
                }

                else if ((stripos($query, 'UPDATE') === 0) || (stripos($query, 'DELETE') === 0))
                {
                    return $result[$hash]->rowCount();
                }

                else if (stripos($query, 'REPLACE') === 0)
                {
                }

                return true;
            }
        }

        return false;
    }
}

Поскольку мне не нужно беспокоиться о коллизиях в запросах, я использовал md5() вместо sha1().

person Alix Axel    schedule 25.01.2010
comment
Мне очень интересно узнать, почему вы приняли решение использовать хеширование MD5. Я опубликовал наивный тест хэширования MD5 по сравнению с собственными массивами PHP и мог найти какое-либо улучшение с помощью метода MD5. - person Inshallah; 25.01.2010
comment
@Inshallah: Решение использовать md5() было просто для того, чтобы у меня не было новых строк (\n) в запросах. - person Alix Axel; 25.01.2010

Насколько мне известно, PDO не использует повторно уже подготовленные операторы, поскольку он не анализирует запрос сам по себе, поэтому он не знает, является ли это одним и тем же запросом.

Если вы хотите создать кеш подготовленных запросов, самым простым способом imho будет md5-хеширование строки запроса и создание таблицы поиска.

OTOH: Сколько запросов вы выполняете (в минуту)? Если меньше нескольких сотен, то вы только усложните код, прирост производительности будет незначительным.

person Morfildur    schedule 25.01.2010
comment
Я могу что-то упустить, но зачем создавать md5 строки запроса? Почему бы не использовать саму строку запроса? - person Inshallah; 25.01.2010
comment
потому что сама строка запроса может быть довольно длинной, поэтому поиск будет медленнее. Хэш позволяет выполнять более быстрый поиск (конечно, если запросы достаточно короткие, а операций поиска достаточно мало, то на самом деле это может быть медленнее, но в обычном случае поиск по хешу выполняется быстрее) - person Morfildur; 25.01.2010
comment
Какая? MD5ing строка запроса для производительности? Это полное безумие :-). Я видел метод индексирования строкой запроса, используемый в Perl, с хорошим эффектом. Вы уверены, что знаете достаточно о том, как реализованы массивы PHP, чтобы дать такую ​​рекомендацию? - person Inshallah; 25.01.2010
comment
Это может быть не нужно для PHP, так как я не проводил каких-либо тестов для этого конкретного случая, но имхо всегда полезно использовать хэш-значения для поиска, даже если сам массив хешируется, как вы могли бы в противном случае поддаться искушению сделать что-то вроде $key = ‹длинный запрос›; foreach ($stuff as $item) { $cache[$key]->do_stuff($item); } что заставит PHP перефразировать запрос для каждой итерации цикла для поиска ключа. OTOH, меня обычно не очень волнует, как сам язык реализует некоторые вещи, поэтому я могу ошибаться: вы должны провести сравнительный анализ и выяснить это. - person Morfildur; 25.01.2010
comment
Я не могу поверить, что PDO не использует повторно уже подготовленные операторы - возможно, RDMS, по крайней мере, отслеживает их. - person Xeoncross; 18.03.2012

Используя хэш MD5 в качестве ключа, вы можете в конечном итоге получить два запроса, которые приводят к одному и тому же хешу MD5. Вероятность не велика, но это может произойти. Не делай этого. Алгоритмы хеширования с потерями, такие как MD5, предназначены только для того, чтобы с высокой степенью достоверности определить, отличаются ли два объекта, но они не являются безопасным средством идентификации чего-либо.

person Daniel Alvarez Arribas    schedule 27.09.2010