Оптимизация запросов (WHERE, GROUP BY, LEFT JOINs) - программирование
Подтвердить что ты не робот

Оптимизация запросов (WHERE, GROUP BY, LEFT JOINs)

Я использую InnoDB.

QUERY, EXPLAIN и INDEXES

SELECT
  stories.*,
  count(comments.id) AS comments,
  GROUP_CONCAT(
    DISTINCT classifications2.name SEPARATOR ';'
  ) AS classifications_name,
  GROUP_CONCAT(
    DISTINCT images.id
    ORDER BY images.position,
    images.id SEPARATOR ';'
  ) AS images_id,
  GROUP_CONCAT(
    DISTINCT images.caption
    ORDER BY images.position,
    images.id SEPARATOR ';'
  ) AS images_caption,
  GROUP_CONCAT(
    DISTINCT images.thumbnail 
    ORDER BY images.position,
    images.id SEPARATOR ';'
  ) AS images_thumbnail,
  GROUP_CONCAT(
    DISTINCT images.medium 
    ORDER BY images.position,
    images.id SEPARATOR ';'
  ) AS images_medium,
  GROUP_CONCAT(
    DISTINCT images.large 
    ORDER BY images.position,
    images.id SEPARATOR ';'
  ) AS images_large,
  GROUP_CONCAT(
    DISTINCT users.id 
    ORDER BY users.id SEPARATOR ';'
  ) AS authors_id,
  GROUP_CONCAT(
    DISTINCT users.display_name 
    ORDER BY users.id SEPARATOR ';'
  ) AS authors_display_name,
  GROUP_CONCAT(
    DISTINCT users.url 
    ORDER BY users.id SEPARATOR ';'
  ) AS authors_url
FROM
  stories 
  LEFT JOIN classifications 
    ON stories.id = classifications.story_id 
  LEFT JOIN classifications AS classifications2 
    ON stories.id = classifications2.story_id 
  LEFT JOIN comments
    ON stories.id = comments.story_id
  LEFT JOIN image_story 
    ON stories.id = image_story.story_id 
      LEFT JOIN images 
        ON images.id = image_story.`image_id` 
  LEFT JOIN author_story 
    ON stories.id = author_story.story_id 
      LEFT JOIN users 
        ON users.id = author_story.author_id 
 WHERE classifications.`name` LIKE 'Home:Top%' 
   AND stories.status = 1
GROUP BY stories.id 
ORDER BY classifications.`name`, classifications.`position`

+----+-------------+------------------+--------+---------------+----------+---------+------------------------+--------+----------------------------------------------+
| id | select_type |      table       |  type  | possible_keys |   key    | key_len |          ref           |  rows  |                    Extra                     |
+----+-------------+------------------+--------+---------------+----------+---------+------------------------+--------+----------------------------------------------+
|  1 | SIMPLE      | stories          | ref    | status        | status   |       1 | const                  | 434792 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | classifications  | ref    | story_id      | story_id |       4 | stories.id             |      1 | Using where                                  |
|  1 | SIMPLE      | classifications2 | ref    | story_id      | story_id |       4 | stories.id             |      1 | Using where                                  |
|  1 | SIMPLE      | comments         | ref    | story_id      | story_id |       8 | stories.id             |      6 | Using where; Using index                     |
|  1 | SIMPLE      | image_story      | ref    | story_id      | story_id |       4 | stories.id             |      1 | NULL                                         |
|  1 | SIMPLE      | images           | eq_ref | PRIMARY       | PRIMARY  |       4 | image_story.image_id   |      1 | NULL                                         |
|  1 | SIMPLE      | author_story     | ref    | story_id      | story_id |       4 | stories.id             |      1 | Using where                                  |
|  1 | SIMPLE      | users            | eq_ref | PRIMARY       | PRIMARY  |       4 | author_story.author_id |      1 | Using where                                  |
+----+-------------+------------------+--------+---------------+----------+---------+------------------------+--------+----------------------------------------------+

+-----------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
|      Table      | Non_unique |  Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type |
+-----------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| stories         |          0 | PRIMARY     |            1 | id          | A         |      869584 | NULL     | NULL   |      | BTREE      |
| stories         |          1 | created_at  |            1 | created_at  | A         |      434792 | NULL     | NULL   |      | BTREE      |
| stories         |          1 | source      |            1 | source      | A         |           2 | NULL     | NULL   | YES  | BTREE      |
| stories         |          1 | source_id   |            1 | source_id   | A         |      869584 | NULL     | NULL   | YES  | BTREE      |
| stories         |          1 | type        |            1 | type        | A         |           2 | NULL     | NULL   |      | BTREE      |
| stories         |          1 | status      |            1 | status      | A         |           2 | NULL     | NULL   |      | BTREE      |
| stories         |          1 | type_status |            1 | type        | A         |           2 | NULL     | NULL   |      | BTREE      |
| stories         |          1 | type_status |            2 | status      | A         |           2 | NULL     | NULL   |      | BTREE      |
| classifications |          0 | PRIMARY     |            1 | id          | A         |         207 | NULL     | NULL   |      | BTREE      |
| classifications |          1 | story_id    |            1 | story_id    | A         |         207 | NULL     | NULL   |      | BTREE      |
| classifications |          1 | name        |            1 | name        | A         |         103 | NULL     | NULL   |      | BTREE      |
| classifications |          1 | name        |            2 | position    | A         |         207 | NULL     | NULL   | YES  | BTREE      |
| comments        |          0 | PRIMARY     |            1 | id          | A         |      239336 | NULL     | NULL   |      | BTREE      |
| comments        |          1 | status      |            1 | status      | A         |           2 | NULL     | NULL   |      | BTREE      |
| comments        |          1 | date        |            1 | date        | A         |      239336 | NULL     | NULL   |      | BTREE      |
| comments        |          1 | story_id    |            1 | story_id    | A         |       39889 | NULL     | NULL   |      | BTREE      |
+-----------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+

ВРЕМЯ ЗАПРОСА

В среднем выполняется 0.035 seconds.

Если я удаляю только GROUP BY, время падает до 0.007 в среднем.

Если я удаляю только фильтр stories.status=1, время падает до 0.025 в среднем. Кажется, что это можно легко оптимизировать.

И если я удалю только фильтр LIKE и ORDER BY, время в среднем сократится до 0.006.

ОБНОВЛЕНИЕ 1: 2013-04-13

Мое понимание улучшило многообразие ответов.

Я добавил индексы author_story и images_story, которые казались улучшенными запросами до 0.025 секунд, но по какой-то странной причине план EXPLAIN выглядит намного лучше. На этом этапе удаление ORDER BY отбрасывает запрос до 0.015 секунд, а при отбрасывании как ORDER BY, так и GROUP BY улучшается производительность запросов до 0.006. Я - это две вещи, на которые нужно сосредоточиться прямо сейчас? Я могу переместить ORDER BY в логику приложения, если это необходимо.

Вот пересмотренные EXPLAIN и INDEXES

+----+-------------+------------------+--------+---------------------------------+----------+---------+--------------------------+------+--------------------------------------------------------+
| id | select_type |      table       |  type  |          possible_keys          |   key    | key_len |           ref            | rows |                         Extra                          |
+----+-------------+------------------+--------+---------------------------------+----------+---------+--------------------------+------+--------------------------------------------------------+
|  1 | SIMPLE      | classifications  | range  | story_id,name                   | name     |     102 | NULL                     |   14 | Using index condition; Using temporary; Using filesort |
|  1 | SIMPLE      | stories          | eq_ref | PRIMARY,status                  | PRIMARY  |       4 | classifications.story_id |    1 | Using where                                            |
|  1 | SIMPLE      | classifications2 | ref    | story_id                        | story_id |       4 | stories.id               |    1 | Using where                                            |
|  1 | SIMPLE      | author_story     | ref    | author_id,story_id,author_story | story_id |       4 | stories.id               |    1 | Using index condition                                  |
|  1 | SIMPLE      | users            | eq_ref | PRIMARY                         | PRIMARY  |       4 | author_story.author_id   |    1 | Using where                                            |
|  1 | SIMPLE      | comments         | ref    | story_id                        | story_id |       8 | stories.id               |    8 | Using where; Using index                               |
|  1 | SIMPLE      | image_story      | ref    | story_id,story_id_2             | story_id |       4 | stories.id               |    1 | NULL                                                   |
|  1 | SIMPLE      | images           | eq_ref | PRIMARY,position_id             | PRIMARY  |       4 | image_story.image_id     |    1 | NULL                                                   |
+----+-------------+------------------+--------+---------------------------------+----------+---------+--------------------------+------+--------------------------------------------------------+


+-----------------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
|      Table      | Non_unique |      Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| author_story    |          0 | PRIMARY            |            1 | id          | A         |      220116 | NULL     | NULL   |      | BTREE      |         |               |
| author_story    |          0 | story_author       |            1 | story_id    | A         |      220116 | NULL     | NULL   |      | BTREE      |         |               |
| author_story    |          0 | story_author       |            2 | author_id   | A         |      220116 | NULL     | NULL   |      | BTREE      |         |               |
| author_story    |          1 | author_id          |            1 | author_id   | A         |        2179 | NULL     | NULL   |      | BTREE      |         |               |
| author_story    |          1 | story_id           |            1 | story_id    | A         |      220116 | NULL     | NULL   |      | BTREE      |         |               |
| image_story     |          0 | PRIMARY            |            1 | id          | A         |      148902 | NULL     | NULL   |      | BTREE      |         |               |
| image_story     |          0 | story_image        |            1 | story_id    | A         |      148902 | NULL     | NULL   |      | BTREE      |         |               |
| image_story     |          0 | story_image        |            2 | image_id    | A         |      148902 | NULL     | NULL   |      | BTREE      |         |               |
| image_story     |          1 | story_id           |            1 | story_id    | A         |      148902 | NULL     | NULL   |      | BTREE      |         |               |
| image_story     |          1 | image_id           |            1 | image_id    | A         |      148902 | NULL     | NULL   |      | BTREE      |         |               |
| classifications |          0 | PRIMARY            |            1 | id          | A         |         257 | NULL     | NULL   |      | BTREE      |         |               |
| classifications |          1 | story_id           |            1 | story_id    | A         |         257 | NULL     | NULL   |      | BTREE      |         |               |
| classifications |          1 | name               |            1 | name        | A         |         128 | NULL     | NULL   |      | BTREE      |         |               |
| classifications |          1 | name               |            2 | position    | A         |         257 | NULL     | NULL   | YES  | BTREE      |         |               |
| stories         |          0 | PRIMARY            |            1 | id          | A         |      962570 | NULL     | NULL   |      | BTREE      |         |               |
| stories         |          1 | created_at         |            1 | created_at  | A         |      481285 | NULL     | NULL   |      | BTREE      |         |               |
| stories         |          1 | source             |            1 | source      | A         |           4 | NULL     | NULL   | YES  | BTREE      |         |               |
| stories         |          1 | source_id          |            1 | source_id   | A         |      962570 | NULL     | NULL   | YES  | BTREE      |         |               |
| stories         |          1 | type               |            1 | type        | A         |           2 | NULL     | NULL   |      | BTREE      |         |               |
| stories         |          1 | status             |            1 | status      | A         |           4 | NULL     | NULL   |      | BTREE      |         |               |
| stories         |          1 | type_status        |            1 | type        | A         |           2 | NULL     | NULL   |      | BTREE      |         |               |
| stories         |          1 | type_status        |            2 | status      | A         |           6 | NULL     | NULL   |      | BTREE      |         |               |
| comments        |          0 | PRIMARY            |            1 | id          | A         |      232559 | NULL     | NULL   |      | BTREE      |         |               |
| comments        |          1 | status             |            1 | status      | A         |           6 | NULL     | NULL   |      | BTREE      |         |               |
| comments        |          1 | date               |            1 | date        | A         |      232559 | NULL     | NULL   |      | BTREE      |         |               |
| comments        |          1 | story_id           |            1 | story_id    | A         |       29069 | NULL     | NULL   |      | BTREE      |         |               |
| images          |          0 | PRIMARY            |            1 | id          | A         |      147206 | NULL     | NULL   |      | BTREE      |         |               |
| images          |          0 | source_id          |            1 | source_id   | A         |      147206 | NULL     | NULL   | YES  | BTREE      |         |               |
| images          |          1 | position           |            1 | position    | A         |           4 | NULL     | NULL   |      | BTREE      |         |               |
| images          |          1 | position_id        |            1 | id          | A         |      147206 | NULL     | NULL   |      | BTREE      |         |               |
| images          |          1 | position_id        |            2 | position    | A         |      147206 | NULL     | NULL   |      | BTREE      |         |               |
| users           |          0 | PRIMARY            |            1 | id          | A         |         981 | NULL     | NULL   |      | BTREE      |         |               |
| users           |          0 | users_email_unique |            1 | email       | A         |         981 | NULL     | NULL   |      | BTREE      |         |               |
+-----------------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+



SELECT
stories.*,
count(comments.id) AS comments,
GROUP_CONCAT(DISTINCT users.id ORDER BY users.id SEPARATOR ';') AS authors_id,
GROUP_CONCAT(DISTINCT users.display_name ORDER BY users.id SEPARATOR ';') AS authors_display_name,
GROUP_CONCAT(DISTINCT users.url ORDER BY users.id SEPARATOR ';') AS authors_url,
GROUP_CONCAT(DISTINCT classifications2.name SEPARATOR ';') AS classifications_name,
GROUP_CONCAT(DISTINCT images.id ORDER BY images.position,images.id SEPARATOR ';') AS images_id,
GROUP_CONCAT(DISTINCT images.caption ORDER BY images.position,images.id SEPARATOR ';') AS images_caption,
GROUP_CONCAT(DISTINCT images.thumbnail ORDER BY images.position,images.id SEPARATOR ';') AS images_thumbnail,
GROUP_CONCAT(DISTINCT images.medium ORDER BY images.position,images.id SEPARATOR ';') AS images_medium,
GROUP_CONCAT(DISTINCT images.large ORDER BY images.position,images.id SEPARATOR ';') AS images_large
FROM
  classifications
  INNER JOIN stories
    ON stories.id = classifications.story_id 
  LEFT JOIN classifications AS classifications2 
    ON stories.id = classifications2.story_id 
  LEFT JOIN comments
    ON stories.id = comments.story_id
  LEFT JOIN image_story 
    ON stories.id = image_story.story_id 
  LEFT JOIN images 
    ON images.id = image_story.`image_id` 
 INNER JOIN author_story 
    ON stories.id = author_story.story_id 
 INNER JOIN users 
    ON users.id = author_story.author_id 
 WHERE classifications.`name` LIKE 'Home:Top%'
   AND stories.status = 1
GROUP BY stories.id 
ORDER BY NULL

ОБНОВЛЕНИЕ 2: 2013-04-14

Я заметил еще одну вещь. Если я не SELECT stories.content (LONGTEXT) и stories.content_html (LONGTEXT), запрос падает с 0.015 секунд до 0.006 секунд. На данный момент я рассматриваю вопрос, могу ли я обойтись без content и content_html или заменить их чем-то другим.

Я обновил запрос, индексы и объяснил в обновленном обновлении 2013-04-13 вместо повторной публикации в этом, поскольку они были незначительными и инкрементальными. Запрос все еще использует filesort. Я не могу избавиться от GROUP BY, но избавился от ORDER BY.

ОБНОВЛЕНИЕ 3: 2013-04-16

В соответствии с запросом я сбросил индексы history_id из image_story и author_story, поскольку они являются избыточными. Результатом было то, что вывод объяснения только изменился, чтобы показать, что параметр possible_keys изменился. Он до сих пор не показывал оптимизацию Using Index.

Также изменен LONGTEXT на TEXT и теперь выберем LEFT(stories.content, 500) вместо stories.content, что делает очень значительную разницу в времени выполнения запроса.

+----+-------------+------------------+--------+-----------------------------+--------------+---------+--------------------------+------+---------------------------------------------------------------------+
| id | select_type |      table       |  type  |        possible_keys        |     key      | key_len |           ref            | rows |                                Extra                                |
+----+-------------+------------------+--------+-----------------------------+--------------+---------+--------------------------+------+---------------------------------------------------------------------+
|  1 | SIMPLE      | classifications  | ref    | story_id,name,name_position | name         |     102 | const                    |   10 | Using index condition; Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | stories          | eq_ref | PRIMARY,status              | PRIMARY      |       4 | classifications.story_id |    1 | Using where                                                         |
|  1 | SIMPLE      | classifications2 | ref    | story_id                    | story_id     |       4 | stories.id               |    1 | Using where                                                         |
|  1 | SIMPLE      | author_story     | ref    | story_author                | story_author |       4 | stories.id               |    1 | Using where; Using index                                            |
|  1 | SIMPLE      | users            | eq_ref | PRIMARY                     | PRIMARY      |       4 | author_story.author_id   |    1 | Using where                                                         |
|  1 | SIMPLE      | comments         | ref    | story_id                    | story_id     |       8 | stories.id               |    8 | Using where; Using index                                            |
|  1 | SIMPLE      | image_story      | ref    | story_image                 | story_image  |       4 | stories.id               |    1 | Using index                                                         |
|  1 | SIMPLE      | images           | eq_ref | PRIMARY,position_id         | PRIMARY      |       4 | image_story.image_id     |    1 | NULL                                                                |
+----+-------------+------------------+--------+-----------------------------+--------------+---------+--------------------------+------+---------------------------------------------------------------------+

innodb_buffer_pool_size
134217728

TABLE_NAME      INDEX_LENGTH
image_story     10010624
image_story     4556800
image_story     0

TABLE_NAME      INDEX_NAMES     SIZE
dawn/image_story    story_image     13921
4b9b3361

Ответ 1

Я сразу вижу две возможности для оптимизации:

Измените ВНУТРЕННУЮ СОЕДИНЕННУЮ СТРАНИЦУ ВНУТРЕННЕЙ РАБОТЫ

В настоящее время ваш запрос сканирует 434792 истории, и вы должны иметь возможность сузить это лучше, полагая, что не каждая история имеет классификацию, соответствующую "Home: Top%". Было бы лучше использовать индекс, чтобы найти нужные вам классификации, а затем просмотреть соответствующие истории.

Но вы используете LEFT OUTER JOIN для классификаций, то есть все истории будут проверяться, имеют ли они соответствующую классификацию или нет. Тогда вы побеждаете, полагая условие классификации в предложении WHERE, эффективно делая обязательным, чтобы была классификация, соответствующая вашему шаблону с помощью LIKE. Таким образом, это уже не внешнее объединение, это внутреннее соединение.

Если вы сначала поместите таблицу классификаций и сделаете ее внутренним соединением, оптимизатор будет использовать это, чтобы сузить поиск историй только теми, которые имеют соответствующую классификацию.

. . . 
FROM
  classifications
  INNER JOIN stories
    ON stories.id = classifications.story_id 
. . .

Предполагается, что оптимизатор сможет выяснить, когда выгодно переупорядочить таблицы, поэтому вам может не потребоваться изменить порядок в вашем запросе. Но в этом случае вам нужно использовать INNER JOIN.

Добавить составные индексы

В ваших таблицах пересечений image_story и author_story нет составных индексов. Часто бывает большим преимуществом добавлять составные индексы к таблицам пересечений в отношениях "многие ко многим", чтобы они могли выполнять объединение и получать оптимизацию "Использование индекса".

ALTER TABLE image_story ADD UNIQUE KEY imst_st_im (story_id, image_id);
ALTER TABLE author_story ADD UNIQUE KEY aust_st_au (story_id, author_id);

Повторите свои комментарии и обновления:

Я не уверен, что вы правильно создали новые индексы. В вашем дампе индексов не отображаются столбцы, и в соответствии с обновленным EXPLAIN новые индексы не используются, что я ожидаю. Использование новых индексов приведет к "Использованию индекса" в дополнительном поле EXPLAIN, что должно помочь производительности.

Вывод SHOW CREATE TABLE для каждой таблицы будет более полной информацией, чем дамп индексов (без имен столбцов), как вы показали.

Вам может потребоваться выполнить ANALYZE TABLE один раз на каждой из этих таблиц после создания индексов. Кроме того, запустите запрос более одного раза, чтобы убедиться, что индексы находятся в пуле буферов. Это таблица InnoDB или MyISAM?

Я также замечаю на выходе EXPLAIN, что столбец rows показывает гораздо меньшее количество затронутых строк. Это улучшение.

Вам действительно нужен ORDER BY? Если вы используете ORDER BY NULL, вы сможете избавиться от "Использование файлового управления" и повысить производительность.


Повторите свое обновление:

Вы по-прежнему не используете оптимизацию "Использование индекса" из таблиц image_story и author_story. Одно из предложений, которое я хотел бы сделать, - устранить избыточные индексы:

ALTER TABLE image_story DROP KEY story_id;
ALTER TABLE author_story DROP KEY story_id;

Причина в том, что любой запрос, который может извлечь выгоду из индекса с одним столбцом в story_id, также может извлечь выгоду из индекса с двумя столбцами (story_id, image_id). Устранение избыточного индекса помогает оптимизатору принять лучшее решение (а также сэкономить некоторое пространство). Это теория, лежащая в основе инструмента, такого как pt-duplicate-key-checker.

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

SHOW VARIABLES LIKE 'innodb_buffer_pool_size'

Проверьте размер индексов для таблицы image_story:

SELECT TABLE_NAME, INDEX_LENGTH FROM INFORMATION_SCHEMA.TABLES 
WHERE TABLE_NAME = 'image_story';

И сравните это с тем, сколько из этих индексов в настоящее время проживает в пуле буферов:

SELECT TABLE_NAME, GROUP_CONCAT(DISTINCT INDEX_NAME) AS INDEX_NAMES, SUM(DATA_SIZE) AS SIZE 
FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE_LRU 
WHERE TABLE_NAME = '`test`.`image_story`' AND INDEX_NAME <> 'PRIMARY'

Конечно, измените `test` выше на имя базы данных, к которой принадлежит ваша таблица.

Эта таблица information_schema является новой в MySQL 5.6. Я предполагаю, что вы используете MySQL 5.6, потому что ваш EXPLAIN показывает "Использование условия индекса", которое также является новым в MySQL 5.6.

Я вообще не использую LONGTEXT, если мне действительно не нужна история с очень длинными строками. Имейте в виду:

  • ТЕКСТ вмещает до 64 КБ
  • MEDIUMTEXT поддерживает до 16 МБ.
  • LONGTEXT поддерживает до 4 ГБ

Ответ 2

Поскольку вы используете MYSQL, вы можете использовать Straight_join

STRAIGHT_JOIN forces the optimizer to join the tables in the order in which they are listed in the FROM clause. You can use this to speed up a query if the optimizer joins the tables in nonoptimal order

Также одной областью улучшения является фильтрация данных таблицы stories, поскольку вам нужны только данные, имеющие статус 1

Итак, в предложении формы вместо добавления всей таблицы stories добавьте только необходимые записи, так как ваш план запроса показывает, что есть строки 434792 и те же для таблицы classification

FROM
   (SELECT 
       * 
   FROM
       STORIES
   WHERE 
       STORIES.status = 1) stories
LEFT JOIN
   (SELECT 
       * 
   FROM
       classifications
   WHERE
       classifications.`name` LIKE 'Home:Top%') classifications
ON stories.id = classifications.story_id 

Также еще одно предложение вы можете увеличить sort_buffer_size, так как вы показываетесь как order by и group by, но будьте осторожны размер вашего буфера по мере увеличения размера буфера для каждого сеанса.

Также, если возможно, вы можете заказать свои записи в своем приложении, если это возможно, как вы уже упоминали. Удаление предложения order by улучшает выполнение только 1/6 части исходного времени...

ИЗМЕНИТЬ

Добавьте индексы в таблицу image_story.image_id для image_story и author_story.story_id для таблицы author_story, так как эти столбцы используются для соединения

Также необходимо создать индекс в images.position, images.id при его использовании.

РЕДАКТИРОВАТЬ 16/4

Я думаю, что вы почти оптимизировали свой запрос, увидев обновление...

Еще одно место, которое вы можете улучшить, - это использовать соответствующий тип данных, о котором упоминал БиллКарвин... Вы можете использовать ENUM или TINYINT для столбцов, таких как статус и другие, которые не имеют каких-либо возможностей для роста, это поможет вам оптимизировать производительность вашего запроса и производительность хранилища вашей таблицы....

Надеюсь, это поможет...

Ответ 3

Вычисление

GROUP_CONCAT(DISTINCT classifications2.name SEPARATOR ';')

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

Поэтому я бы рекомендовал использовать временную таблицу для этой информации. Кроме того, чтобы избежать вычисления условия LIKE дважды (один раз для временной таблицы и один раз для "реального" запроса), я также создавал бы временную таблицу для этого.

Исходный запрос в очень упрощенной версии (без изображений и таблицы пользователей, чтобы было легче читать):

SELECT
    stories.*,
    count(DISTINCT comments.id) AS comments,
    GROUP_CONCAT(DISTINCT classifications2.name ORDER BY 1 SEPARATOR ';' )
        AS classifications_name
FROM
    stories
    LEFT JOIN classifications
        ON stories.id = classifications.story_id
    LEFT JOIN classifications AS classifications2
        ON stories.id = classifications2.story_id
    LEFT JOIN comments
        ON stories.id = comments.story_id
WHERE
    classifications.`name` LIKE 'Home:Top%'
        AND stories.status = 1
GROUP BY stories.id
    ORDER BY stories.id, classifications.`name`, classifications.`positions`;

Я бы заменил его следующими запросами: временными таблицами _tmp_filtered_classifications (идентификаторы классификаций с именем LIKE Home: Top% ') и _tmp_classifications_of_story (для каждого идентификатора истории, содержащегося в _tmp_filtered_classifications, все названия классификации):

DROP TABLE IF EXISTS `_tmp_filtered_classifications`;

CREATE TEMPORARY TABLE _tmp_filtered_classifications
    SELECT id FROM classifications WHERE name LIKE 'Home:Top%';

DROP TABLE IF EXISTS `_tmp_classifications_of_story`;

CREATE TEMPORARY TABLE _tmp_classifications_of_story ENGINE=MEMORY
    SELECT stories.id AS story_id, classifications2.name
        FROM 
        _tmp_filtered_classifications
        INNER JOIN classifications        
            ON _tmp_filtered_classifications.id=classifications.id
        INNER JOIN stories
            ON stories.id = classifications.story_id
        LEFT JOIN classifications AS classifications2
            ON stories.id = classifications2.story_id
        GROUP BY 1,2;

SELECT
    stories.*,
    count(DISTINCT comments.id) AS comments,
    GROUP_CONCAT(DISTINCT classifications2.name ORDER BY 1 SEPARATOR ';')
        AS classifications_name
FROM
    _tmp_filtered_classifications
    INNER JOIN classifications        
        ON _tmp_filtered_classifications.id=classifications.id
    INNER JOIN stories
        ON stories.id = classifications.story_id
    LEFT JOIN _tmp_classifications_of_story AS classifications2
        ON stories.id = classifications2.story_id
    LEFT JOIN comments
        ON stories.id = comments.story_id
WHERE
    stories.status = 1
GROUP BY stories.id
    ORDER BY stories.id, classifications.`name`, classifications.`positions`;

Обратите внимание, что я добавил еще несколько предложений "упорядочить по" в ваш запрос, чтобы проверить, что оба запроса предоставляют одинаковые результаты (с использованием diff). Я также изменил count(comments.id) на count(DISTINCT comments.id), в противном случае количество комментариев, вычисляемых запросом, неверно (опять же, из-за объединений, которые умножают количество строк).

Ответ 4

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

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

Если вам нужно какое-либо разъяснение этой стратегии, дайте мне знать.

** Обновление: аналогичная тактика, используемая, когда каждая операция соответствует примерно одному и тому же проценту данных, как и другие операции, - это время каждой операции отдельно, а затем сначала запускать операцию, которая использует наименьшее количество времени. Некоторые поисковые операции быстрее других, и самые быстрые из них должны быть первыми, если все остальные факторы равны. Таким образом, более медленные операции поиска будут иметь меньше данных для работы, и общий результат будет более высокой.

Ответ 5

Моя ставка заключается в том, что условие LIKE является наихудшей по вашему запросу. Вы уверены, что вам нужно это сделать?

4 шага:

  • Создайте столбец с индексированием IsHomeTop bool в таблице classifications
  • Запустить UPDATE classifications SET IsTopHome = 1 WHERE NAME LIKE 'Home:Top%'
  • Запустите свой начальный запрос с помощью WHERE classifications.IsTopHome == 1
  • Enjoy

Ваш запрос слишком критичен, чтобы оператор LIKE снизил вашу производительность.
И если stories обновляется много, я не думаю, что это относится к вашей таблице classifications. Поэтому дайте вам шанс и уничтожьте оператор LIKE.

Ответ 6

Некоторые способы вы можете попробовать здесь:

1) create covering index on classifications.`name`

Вы можете ускорить запрос, создав индекс покрытия.

Индекс покрытия относится к случаю, когда все поля, выбранные в запросе, покрываются индексом, в этом случае InnoDB (не MyISAM) никогда не будет считывать данные в таблице, а только использовать данные в индексе, значительно ускорение выбора.

CREATE TABLE classifications ( KEY class_name (name,... все столбцы) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;

2) вместо classifications.name LIKE 'Home:Top%' используйте locate('Home:Top',classifications.name)