Оптимизация страницы PHP: узкое место MySQL - PullRequest
2 голосов
/ 26 апреля 2009

У меня есть страница, загрузка которой занимает 37 секунд. В то время как это загружает, это привязывает использование ЦПУ MySQL через крышу. Я не написал код для этой страницы, и он довольно запутанный, поэтому причина узкого места для меня не очевидна.

Я профилировал его (используя kcachegrind) и обнаружил, что большая часть времени на странице тратится на выполнение запросов MySQL (90% времени тратится на 25 различных вызовов mysql_query).

Запросы принимают форму следующего с изменением tag_id для каждого из 25 различных вызовов:

SELECT * FROM tbl_news WHERE news_id
 IN (select news_id from
 tbl_tag_relations WHERE tag_id = 20)

Каждый запрос занимает около 0,8 секунды, чтобы завершить его с несколькими более длительными задержками, чтобы обеспечить достаточную меру ... таким образом, 37 секунд для полной загрузки страницы.

У меня вопрос, является ли способ, которым запрос форматируется с помощью этого вложенного выбора, вызывает проблему? Или это может быть одна из миллиона других вещей? Любой совет о том, как подойти к решению этой медлительности приветствуется.

Запуск EXPLAIN в запросе дает мне это (но я не совсем понимаю влияние этих результатов ... NULL на первичный ключ выглядит так, как будто это будет плохо, да? Количество возвращаемых результатов мне кажется высоким а в конце возвращается только несколько результатов):

1    PRIMARY     tbl_news   ALL NULL    NULL    NULL    NULL    1318    Using where
2   DEPENDENT SUBQUERY  tbl_tag_relations   ref FK_tbl_tag_tags_1   FK_tbl_tag_tags_1   4   const   179 Using where

Ответы [ 5 ]

5 голосов
/ 26 апреля 2009

Я обратился к этому вопросу в Ошибки разработки баз данных, сделанные AppDevelopers . В основном, фавор присоединяется к агрегации. IN не является агрегацией как таковой, но применяется тот же принцип. Хорошая оптимизация сделает эти два запроса эквивалентными по производительности:

SELECT * FROM tbl_news WHERE news_id
 IN (select news_id from
 tbl_tag_relations WHERE tag_id = 20)

и

SELECT tn.*
FROM tbl_news tn
JOIN tbl_tag_relations ttr ON ttr.news_id = tn.news_id
WHERE ttr.tag_id = 20

Как я полагаю, Oracle и SQL Server оба делают, а MySQL нет. Вторая версия в основном мгновенная. С сотнями тысяч строк я провел тест на своей машине и получил первую версию с точностью до секунды, добавив соответствующие индексы. Версия объединения с индексами в основном мгновенная, но даже без индексов работает нормально.

Кстати, приведенный выше синтаксис, который я использую, - тот, который вы предпочитаете для выполнения объединений. Это понятнее, чем помещать их в предложение WHERE (как предлагали другие), и приведенные выше действия могут выполнять определенные действия способом ANSI SQL с левыми внешними объединениями, чего не могут выполнить условия WHERE.

Итак, я бы добавил индексы для следующего:

  • tbl_news (news_id)
  • tbl_tag_relations (news_id)
  • tbl_tag_relations (tag_id)

и запрос будет выполнен практически мгновенно.

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

3 голосов
/ 26 апреля 2009

Сам SQL-запрос определенно является вашим узким местом. Запрос содержит подзапрос, который является частью кода IN (...). По сути, это запуск двух запросов одновременно. Скорее всего, вы можете вдвое (или больше!) Сократить время SQL с помощью JOIN (аналогично тому, что упоминал d03boy выше) или более целенаправленного запроса SQL. Примером может быть:

SELECT * 
FROM tbl_news, tbl_tag_relations 
WHERE tbl_tag_relations.tag_id = 20 AND
tbl_news.news_id = tbl_tag_relations.news_id 

Чтобы SQL работал быстрее, вы также должны стараться избегать использования SELECT * и выбирать только ту информацию, которая вам нужна; также поместите ограничительное заявление в конце. например:

SELECT news_title, news_body 
... 
LIMIT 5;

Вам также захочется взглянуть на саму схему базы данных. Убедитесь, что вы индексируете все часто упоминаемые столбцы, чтобы запросы выполнялись быстрее. В этом случае вы, вероятно, захотите проверить поля news_id и tag_id.

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

2 голосов
/ 26 апреля 2009

Если я правильно понимаю, это просто список новостей для определенного набора тегов.

  1. Прежде всего, вы действительно не должны когда-либо SELECT *

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

  3. Лучшим подходом к использованию IN может быть использование JOIN с условием WHERE. При использовании IN это будет в основном много OR операторов.
  4. Ваш tbl_tag_relations обязательно должен иметь индекс на tag_id
1 голос
/ 26 апреля 2009

К сожалению, MySQL не очень хорошо работает с некоррелированными подзапросами, как показывает ваш случай. План в основном говорит, что для каждой строки во внешнем запросе будет выполнен внутренний запрос. Это быстро выйдет из-под контроля. Переписывание как простое старое объединение, как уже упоминали другие, обойдет эту проблему, но затем может вызвать нежелательный эффект дублирующихся строк.

Например, исходный запрос будет возвращать 1 строку для каждой соответствующей строки в таблице tbl_news, но этот запрос:

SELECT news_id, name, blah
FROM tbl_news n
JOIN tbl_tag_relations r ON r.news_id = n.news_id
WHERE r.tag_id IN (20,21,22)

вернет 1 строку для каждого соответствующего тега. Вы можете добавить туда DISTINCT, который будет иметь минимальное влияние на производительность в зависимости от размера набора данных.

Не слишком троллинг, но большинство других баз данных (PostgreSQL, Firebird, Microsoft, Oracle, DB2 и т. Д.) Будут обрабатывать исходный запрос как эффективное полусоединение. Лично я нахожу синтаксис подзапроса намного более читабельным и более простым для написания, особенно для больших запросов.

1 голос
/ 26 апреля 2009
select * 
 from tbl_news, tbl_tag_relations 
 where 
      tbl_tag_relations.tag_id = 20 and 
      tbl_news.news_id = tbl_tag_relations.news_id 
 limit 20

Я думаю, что это дает те же результаты, но я не уверен на 100%. Иногда помогает простое ограничение результатов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...