Нет преимущества в скорости с подготовленными заявлениями? - PullRequest
3 голосов
/ 22 марта 2012

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

Ситуация - это таблица результатов турнира.Есть несколько школ, участвующих в нескольких мероприятиях, и каждая школа имеет оценку для каждого события.Чтобы получить оценки отдельных школ по всем событиям, требуется SQL-запрос с LEFT JOIN, например:

SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`=:school_id;

. Я написал два сценария PHP-теста для запуска с образцами данных (200 событий), используянативные объекты PDO (prepare() / bindValue() / execute() против query()):

EDIT Модифицированные тесты с предложениями, приведенными ниже (для запроса ванили требуется выборка, выборка различных идентификаторов,и связать готовить вне цикла).Только дает скромное преимущество в скорости готовым операторам сейчас:

Готовое заявление:

$start = microtime(true);
$sql = 'SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`=:school_id';
echo $sql."<br />\n";
$stmt = $db->prepare($sql);
$sid = 0;
$stmt->bindParam(':school_id', $sid);
for ($i=0; $i<$max; $i++) {
    $sid = rand(1,499);
    $stmt->execute();
    $rs = $stmt->fetchAll();
}
$delta = bcsub(microtime(true), $start, 4);
echo "<strong>Overall time:</strong> $delta<br />\n";
echo "<strong>Average time:</strong> ".($delta/$max)."<br />\n";

Ванильный запрос:

set_time_limit(15); // Add time for each run
$start = microtime(true);
$sql = 'SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`={$sid}';
echo $sql."<br />\n";
for ($i=0; $i<$max; $i++) {
    $sid = rand(1,499);
    $stmt = $db->query("SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`={$sid}");
    $rs = $stmt->fetchAll();
}
$delta = bcsub(microtime(true), $start, 4);
echo "<strong>Overall time:</strong> $delta<br />\n";
echo "<strong>Average time:</strong> ".($delta/$max)."<br />\n";

Я снова и снова получаю оценки событий в одной и той же школе (ID школы № 10) и, установив $max на 10 000, я получаю результаты, которые показывают, что ванильные запросы выполняются на 30% быстрее (25,72 секунды против 36,79).Я делаю это неправильно, или это точно, что подготовленные операторы не быстрее даже в повторяющейся ситуации?

EDIT обновленные тесты теперь получают 33,95 секунды против 34,10 ванили.Huzzah, подготовленные заявления быстрее.Но только на долю секунды за 10 000 итераций.Возможно, потому что мой запрос не настолько сложен (подготовленные операторы кэшируют дерево разбора для их преимущества)?Или здесь еще есть что оптимизировать?

Ответы [ 3 ]

4 голосов
/ 22 марта 2012

Ванильный запрос каждый раз выполняет один и тот же запрос, поэтому вы просто тестируете время «выборки результатов из кэша запросов», а не фактическое время выполнения запроса. Это недействительный тест.

Вам нужно выполнить построение запроса ВНУТРИ цикла, поэтому вы каждый раз выполняете запрос NEW:

for ($i=0; $i<$max; $i++) {
    $sql = <<<EOL
SELECT e.id, e.name, c.competing, c.raw, c.final
FROM events e
LEFT JOIN scores c ON e.id=c.event_id
WHERE c.school_id= $i;
EOL;
    $rs = $db->query($sql);
}
1 голос
/ 06 сентября 2012

Никто из этих людей явно не указал явно:

PDO эмулирует подготовленные операторы с MySQL по умолчанию , даже если драйвер их поддерживает.

Это полная противоположность того, что говорится на странице руководства PHP PDO , но просмотрите исходный код, и вы увидите, что они всегда,де-факто, используйте эмулированные подготовленные операторы (я думал, что это ошибка кодирования, но когда я подал отчет, он был классифицирован как WONT_FIX, и у меня был какой-то Zend dev, просто заявляющий: «Мы всегда стремимся эмулировать», просто потому. "Что не имеет смысла для меня, но хорошо.


Чтобы использовать действительно подготовленные высказывания, вы должны прыгнуть через обруч. Если вам не нравится это, обвиняйтеразработчики Zend, так как исправление выглядит так, как будто это заняло бы все 5 минут (просто переместите, если все вокруг).

$pdo = new PDO($dsn, $user, $pass, array(ATTR::PDO_EMULATE_PREPARES => false));

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

0 голосов
/ 22 марта 2012

Возможно, вы не сравниваете яблоки с яблоками.

PDO::query() Выполняет оператор SQL, возвращая набор результатов в виде объекта PDOStatement.

Чтобы получить фактические результаты, вам нужно перебрать возвращенный объект или, как и в случае с подготовленным оператором, вызвать fetchAll(), чтобы загрузить весь набор результатов в массив

Вероятно, правильный цикл запроса ванилизатем:

for ($i=0; $i<$max; $i++) {
    $stmt = $db->query($sql);
    $rs = $stmt->fetchAll();
}

или, альтернативно, удаление вызова fetchAll() из подготовленного цикла оператора.

Вы также можете уменьшить вызовы метода, необходимые для подготовленного оператора, используя вместо этого bindParam()из bindValue()

$school_id = null;
$stmt->bindParam(':school_id', $school_id);
for ($i=0; $i<$max; $i++) {
    $school_id = 10;
    $stmt->execute();
    $rs = $stmt->fetchAll();
}
...