PHP PDO: как переподготовка оператора влияет на производительность - PullRequest
9 голосов
/ 25 января 2010

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

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

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

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

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

Ответы [ 5 ]

6 голосов
/ 25 января 2010

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

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

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

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

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

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

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

4 голосов
/ 25 января 2010

Поверьте, я делал это до и после создания кэша подготовленных операторов, прирост производительности был очень заметным - см. Этот вопрос: Подготовка операторов 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().

1 голос
/ 25 января 2010

ОК, так как я избивал методы ввода запросов к кешу, кроме простого использования самой строки запроса, я сделал наивный тест. Ниже приведено сравнение с использованием простой строки запроса и первого создания хеша 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";
1 голос
/ 25 января 2010

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

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

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

0 голосов
/ 27 сентября 2010

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

...