Медленные запросы к базе данных - PullRequest
0 голосов
/ 12 июля 2020

Так как база данных моего веб-сайта сильно выросла, производительность некоторых запросов стала ужасной. Некоторые запросы выполняются более 30 секунд. Мне интересно, может ли кто-нибудь помочь мне оптимизировать мой запрос или предложить, как я могу улучшить производительность? Я установил индекс для всех внешних ключей и идентификаторов.

SELECT p.*
     , u.unique_id
     , u.nick_name
     , u.avatar_thumb
     , t.desc as tag_desc
     , pt.post_id as tag_post_id 
  from tt_post_tags pt
  LEFT 
  JOIN tt_posts p
    ON p.id = pt.post_id
 RIGHT 
  JOIN tt_users u 
    ON p.user_id = u.user_id
  LEFT 
  JOIN tt_tags t
    ON t.name = "gameday"
 WHERE pt.name = "gameday"
 ORDER 
    BY create_date DESC
 LIMIT 100

Выполнение вышеуказанного запроса занимает 29 секунд. Если я удалю «create_date DES C» из запроса, он выполняется за 0,3 секунды. Я добавил индекс для create_date, но все же для выполнения запроса требуется 30 секунд. Таблица tt_posts содержит около 1,6 миллиона записей.

В моей базе данных есть следующие таблицы: Сообщения, Пользователи, Теги и PostTags.

Таблица сообщений содержит внешний ключ для таблица пользователей.

Таблица тегов содержит уникальный идентификатор и имя для каждого тега

Таблица Post_tags также содержит внешний ключ из таблицы тегов в качестве внешнего ключа для сообщения, для которого предназначен этот тег.

Я могу завтра включить диаграмму, если ее будет сложно понять. Надеюсь, кто-нибудь сможет мне помочь. Заранее спасибо.

CREATE TABLE `tt_posts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `post_id` bigint(30) NOT NULL,
  `user_id` bigint(30) NOT NULL,
  `create_date` datetime NOT NULL,
  `cover` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `duration` int(10) DEFAULT NULL,
  `desc` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
  PRIMARY KEY (`id`),
  UNIQUE KEY `post_id` (`post_id`),
  KEY `user_id` (`user_id`),
  KEY `create_date` (`create_date`)
) ENGINE=InnoDB AUTO_INCREMENT=4641550 DEFAULT CHARSET=utf8

Объясните выберите

CREATE TABLE `tt_tags` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `tt_tag_id` BIGINT(30) NULL DEFAULT NULL,
    `name` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
    PRIMARY KEY (`id`),
    UNIQUE INDEX `name` (`name`),
    UNIQUE INDEX `tt_tag_id` (`tt_tag_id`),
    INDEX `tt_tag_id_key` (`tt_tag_id`),
    INDEX `name_key` (`name`)
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB

И

CREATE TABLE `tt_post_tags` (
    `post_id` INT(11) NOT NULL,
    `name` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
    INDEX `post_id` (`post_id`),
    INDEX `name` (`name`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

И

CREATE TABLE `tt_users` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
    `user_id` BIGINT(30) NOT NULL,
    `unique_id` VARCHAR(190) NOT NULL COLLATE 'utf8mb4_unicode_ci',
    `nick_name` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
    `avatar` VARCHAR(190) NOT NULL COLLATE 'utf8mb4_unicode_ci',
    `signature` TEXT NOT NULL COLLATE 'utf8mb4_unicode_ci',
    PRIMARY KEY (`id`),
    UNIQUE INDEX `user_id` (`user_id`),
    UNIQUE INDEX `unique_id` (`unique_id`),
    INDEX `unique_id_index` (`unique_id`),
    INDEX `user_id_index` (`user_id`)
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB

Ответы [ 2 ]

1 голос
/ 12 июля 2020

На мой взгляд, основная проблема с вашим запросом - это сочетание левого и правого внешних соединений. Честно говоря, вы можете это правильно прочитать?

Одно только первое соединение кажется странным. Вы присоединяете пост к его тегам поста. Но может ли тег сообщения существовать без сообщения? К чему бы это относилось? (Было бы более разумно и наоборот: также выбирать сообщения, у которых нет тегов.) Если я не ошибаюсь, ваше соединение преобразуется в простое внутреннее соединение. В предложении where вы дополнительно ограничиваете этот результат тегами публикации с именем 'gameday'.

Затем вы выполняете правое внешнее соединение с пользователями. Мы избегаем правых внешних объединений, поскольку они гораздо менее читабельны, чем левые внешние объединения, но что ж, вы выбираете всех пользователей, даже тех, у кого нет тегов сообщений 'gameday'.

Затем вы оставили внешнее объединение для всех тегов 'gameday' . Это выглядит совершенно не связанным с другими таблицами (т.е. вы либо находите теги «gameday», либо нет). Но в своем объяснении вы говорите: «Таблица Post_tags содержит внешний ключ из тегов», поэтому я предполагаю, что в вашей таблице тегов сообщений нет tag_id, но на самом деле имя - это идентификатор тега (и, следовательно, также внешний ключ в тегах сообщений стол). Это снова приводит к вопросу: зачем вообще существовать тег поста, если он не имеет связанного тега? Вероятно, это невозможно, и снова все сводится к простому внутреннему соединению. (Я бы порекомендовал здесь иметь tag_id вместо имен в обеих таблицах, просто для удобства чтения. Имя столбца name как бы скрывает отношения внешнего ключа.)

В вашем запросе вы не используете не показывает никакой информации из таблицы тегов сообщений, но я вижу, что вы выбрали pt.post_id as tag_post_id, что, конечно же, снова просто p.id as tag_post_id. Я полагаю, это опечатка, и вы хотите вместо этого показать pt.id as tag_post_id?

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

Ваш create_date не соответствует таблице. Полагаю, это столбец в таблице сообщений?

Это вопрос, который я задаю:

select
  gdp.*,
  u.unique_id,
  u.nick_name,
  u.avatar_thumb
from tt_users u 
left join
(
  select
    p.*,
    t.desc as tag_desc,
    pt.id as tag_post_id
  from tt_tags t
  join tt_post_tags pt on pt.name = t.name
  join tt_posts p on p.id = pt.post_id
  where t.name = 'gameday'
) gdp on gdp.user_id = u.user_id
order by p.create_date desc;

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

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

  1. Нам нужны только теги 'gameday'. Поскольку это, по-видимому, первичный ключ для tt_tags, уже должен быть уникальный индекс для tt_tags(name).
  2. Поскольку это внешний ключ, также должен быть индекс для tt_post_tags(name). Это хорошо, но, поскольку мы хотим продолжить присоединение к post_id, было бы полезно иметь и это в индексе: create unique index idx on tt_post_tags(name, post_id). Однако, поскольку это естественный ключ таблицы, этот индекс также должен существовать, чтобы гарантировать целостность данных. Если его еще нет, поспешите предоставить.
  3. Наконец, мы присоединяем tt_posts по его первичному ключу (т.е. должен быть индекс по tt_posts(id)). Еще раз: нам здесь нечего делать.

Вы выбираете всех пользователей и выбираете все теги 'gameday'. Затем вы должны присоединить все найденные теги к пользователям, что уже является некоторой работой. Вы можете представить это как упорядочение всех найденных тегов по user_id, чтобы присоединиться. Затем вы хотите отсортировать результат по дате публикации. Это означает, что СУБД должна снова отсортировать все строки результатов. Сортировка требует времени; так оно и есть. Сколько строк в результате? Если мы говорим о миллионах строк для сортировки, то это, вероятно, останется медленным. И если многие пост-теги являются тегами «игрового дня», то даже индексы могут не сильно помочь при чтении таблиц, а вместо этого СУБД может go для полного последовательного чтения таблицы. Убедитесь, что статистика актуальна (https://dev.mysql.com/doc/refman/8.0/en/analyze-table.html).

0 голосов
/ 14 июля 2020

(Первый этап ответа на вопрос)

Сначала давайте посмотрим на запрос без users:

select  p.id
    from  post_tags AS pt
    join  posts     AS p   ON p.id = pt.post_id
    join  tags      AS t   ON t.name = "gameday"
    where  pt.name = "gameday"
    ORDER BY p.create_date
    LIMIT 100;

Невозможно иметь один индекс, который обрабатывает оба pt.name и p.create_date. Есть ли способ поместить их в одну таблицу? Я вижу, например, что name кажется избыточным в t и p.

tt_post_tags звучит как таблица сопоставления многие-ко-многим между сообщениями и тегами; это? Если да, то что такое name, кажется, что это tags и post_tags?

Я думаю, что

 join  tags      AS t   ON t.name = "gameday"

должно быть

 join  tags      AS t   ON t.name = "gameday"  AND pt.tag_id = t.tag_id

Если да, то это может быть основной проблемой. Для остальных таблиц укажите SHOW CREATE TABLE.

Следующие индексы могут (или не могут) помочь:

tags:  (post_id, name)
tags:  (name, tag_id)
posts:  (create_date, id)
post_tags:  (name, post_id)

Подробнее

A UNIQUE INDEX - это INDEX, поэтому второй из них является избыточным и должен быть удален: UNIQUE(x), INDEX(x)

Index Cookbook: http://mysql.rjweb.org/doc.php/index_cookbook_mysql

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