Поиск слабых мест производительности: анализируем SQL-запросы
Меня всегда интересовало, насколько эффективно WordPress работает с базой данных, и насколько хорошо спроектирована база данных.
Практически в каждом проекте, над которым я работаю, я использую те или иные средства для анализа производительности скрипта и поиска его слабых мест. Для разработчиков не является секретом, что во многих случаях плохая производительность работы скрипта обусловлена низкой производительностью SQL-запросов. И, как правило, низкое быстродействие запросов связано с их неоптимальностью (что включает в себя отсутствие необходимых индексов в базе данных).
Однажды столкнувшись с ужасной производительностью WordPress и не имея возможности анализировать код десятка поставленных плагинов, я стал решать задачу иначе, в результате чего родился плагин для анализа SQL-запросов.
Принцип работы. Плагин устанавливает обработчики действий, которые позволяют перехватить запрос к базе данных непосредственно перед его выполнением. Если перехваченный запрос — SELECT
, UPDATE
или DELETE
, то скрипт пытается его "объяснить" — выполнить EXPLAIN
над запросом. Предвидя возражения, что EXPLAIN
работает только с SELECT
, но никак не с DELETE
и UPDATE
, поясняю: запросы DELETE
и UPDATE
переписываются в SELECT
, например:
FROM `t1`
INNER JOIN `t2`
ON `t1`.`id` = `t2`.`id`
WHERE `t2`.`key` = 'value'
LIMIT 5
будет переписан в
FROM `t1`
INNER JOIN `t2`
ON `t1`.`id` = `t2`.`id`
WHERE `t2`.`key` = 'value'
LIMIT 5
Возможно, переписывание работает не всегда, но такие случаи мне еще не встречались
Я предполагаю, что Вы понимаете, для чего нужен EXPLAIN
и как следует понимать результаты, которые он выдаёт. Если это не так, то очень рекомендую прочитать статью "Optimizing Queries with EXPLAIN", а затем вернуться к данной статье.
Плагин в футере темы (кстати, это относится и к админке) выдаст подробный лог запросов с их объяснением. Это может выглядеть примерно так:
Установка. К сожалению, установка данного плагина полностью не автоматизируется: кое-что надо делать вручную. После активации плагина в WordPress нужно выполнить следующие действия:
- Добавить в файл
/wp-config.php
следующие строки:[-]View Code PHPdefine('SQLMON_ENABLED', true);
$sqlmon_allowed_ips = array('70.87.222.86', '195.10.218.132', '127.0.0.1'); - Далее предстоит пропатчить один файлик WordPress:
/wp-includes/wp-db.php
. Сразу объясню, зачем это нужно: WordPress не предоставляет нормальных возможностей отловить запрос до его выполнения. Фильтрquery
для наших целей не подходит: во-первых, последующий плагин может переписать запрос, во-вторых, я уже насмотрелся на плагины, которые лезут переписывать запрос, если видят SELECT. Проверено, что переписывание EXPLAIN SELECT такими плагинами приводит к синтаксической ошибке.Теперь о том, что нужно менять. В файле
/wp-includes/wp-db.php
есть класс wpdb, в котором есть метод query. В этом методе нужно найти строки[-]View Code PHP// Perform the query via std mysql_query function..
if (SAVEQUERIES)
$this->timer_start();
$this->result = @mysql_query($query, $this->dbh);
++$this->num_queries;и изменить их:
[-]View Code PHP// Perform the query via std mysql_query function..
if (SAVEQUERIES)
$this->timer_start();
if (true == function_exists('do_action')) {
do_action('before_query', $query, $this->dbh);
}
$this->result = @mysql_query($query, $this->dbh);
if (true == function_exists('do_action')) {
do_action('after_query', $query, $this->dbh);
}
++$this->num_queries;Для тех, кто предпочитает иметь дело с патчами, привожу патч в формате unified diff (внимание: патч проверялся только на WordPress 2.5.1):
[-]Download wp-db-2.5.1.php.diff--- wp-db.old.php 2008-03-21 01:34:32.000000000 +0200
+++ wp-db.php 2008-06-13 03:02:27.000000000 +0300
@@ -272,7 +272,16 @@
if (SAVEQUERIES)
$this->timer_start();
+ if (true == function_exists('do_action')) {
+ do_action('before_query', $query, $this->dbh);
+ }
+
$this->result = @mysql_query($query, $this->dbh);
+
+ if (true == function_exists('do_action')) {
+ do_action('after_query', $query, $this->dbh);
+ }
+
++$this->num_queries;
if (SAVEQUERIES)
После этого перезагружаем страницу и смотрим
Update: для WordPress 2.6 патч будет выглядеть следующим образом:
+++ wp-db.php 2008-08-03 10:39:14.000000000 +0300
@@ -605,7 +605,16 @@
if ( defined('SAVEQUERIES') && SAVEQUERIES )
$this->timer_start();
+ if (true == function_exists('do_action')) {
+ do_action('before_query', $query, $this->dbh);
+ }
+
$this->result = @mysql_query($query, $this->dbh);
+
+ if (true == function_exists('do_action')) {
+ do_action('after_query', $query, $this->dbh);
+ }
+
++$this->num_queries;
if ( defined('SAVEQUERIES') && SAVEQUERIES )
[...] “13 Тэгов, которые следует удалить из вашей темы” SqlMon: плагин для анализа SQL-запросов » Июнь 12, [...]
[...] был получен при помощи плагина SQL Monitor (в целях повышения удобочитаемости, я изменил [...]
Для WordPress 2.6.1 можно использовать тот же самый патч, что и для WordPress 2.6.
Млять, написал такой длинный коммент и не сохранил, как обычно делаю. Получил в результате облом по проверочному коду - время утекло…
Плагин архиполезный, однозначно.
Ставил на WP 2.3.3.
1. В моей теме не хочет выводится, в админке ОК. При переключении на дефолтную - все ОК. Может где-то надо путь к теме указать?
2. Очень бы пригодился небольшой ФАК по работе с плагином. В частности интересует, что выводится в графе Results - количество запросов к базе?
Сейчас пытаюсь разобраться, как мне увидеть, что запрос, например:
SELECT option_value FROM wp_options WHERE option_name = ‘kubrick_header_image’ LIMIT 1
потребовал столько-то соединений с базой данных, столько-то выделенной памяти и столько-то времени на выполнение…
Сорри за ламерские вопросы.
Кстати, возвращаясь к началу своего коммента - при защите от спама, описанной на моем блоге (URL в адресе) капча не нужна, обломов (как у меня произошло) не бывает, и защита от спама 100% - проверено уже годами. Рекомендую.
Сорри, как-то я пропустил комментарий
wp_footer()
.Используется одно соединение
Графа Time.
С этим сложнее — данные выбираются уже после того, как отработает функция.
На мой взгляд, самым полезным является результат в таблице под запросом (EXPLAIN запроса).
Очень рекомендую прочитать Optimizing Queries with EXPLAIN