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

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

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

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

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

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

Хорошо, так как я искал методы ввода запросов к кешу, кроме простого использования самой строки запроса, я сделал наивный критерий. Следующие сравниваются с использованием простой строки запроса и первого создания хеша 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"; 

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

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

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

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

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

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

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

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

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

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

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

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