Как оптимизировать это соединение mysql на большой таблице?

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

Я вставляю сообщения в таблицу posts после завершения сканирования и назначаю им feed_id для идентификации источника. это структура таблицы posts (усеченная):

CREATE TABLE `posts` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `feed_id` int(11) NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT NULL,
  `identifier` varchar(255) DEFAULT NULL,
  `published` timestamp NULL DEFAULT NULL,
  `content` longtext,
  ...
  ...
  `is_unread` int(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Каждый администратор (пользователь) имеет доступ к одному или нескольким каналам. Итак, на странице создания информационного бюллетеня я хочу показать им список сообщений из каналов, которые им разрешено просматривать, а также я показываю кнопку для размещения сообщений в определенных категориях этого информационного бюллетеня, если пользователь ранее выбрал это сообщение, я должен покажите ему это и пусть он удалит это из категории. Так что у меня есть и другие таблицы: newsletters, categories, newsletter_post, category_post. Вот их структуры:

newsletters:

CREATE TABLE `newsletters` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT NULL,
  `sent_at` timestamp NULL DEFAULT NULL,
  `title` varchar(255) DEFAULT NULL,
  `date` date DEFAULT NULL,
  `topic_id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

categories:

CREATE TABLE `categories` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `topic_id` int(11) NOT NULL,
  `title` varchar(255) DEFAULT NULL,
  `slug` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

newsletter_post:

CREATE TABLE `newsletter_post` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT NULL,
  `newsletter_id` int(11) NOT NULL,
  `post_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

category_post:

CREATE TABLE `category_post` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT NULL,
  `category_id` int(11) NOT NULL,
  `post_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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

SELECT DISTINCT `posts`.`id`, `published`, `posts`.`title`, `posts`.`content`, `source_name`, `category_id`, `newsletter_id`, `link_href`, categories.title as category_title
FROM `posts`
LEFT JOIN `category_post` ON `posts`.`id` = `category_post`.`post_id`
LEFT JOIN `categories` ON `categories`.`id` = `category_post`.`category_id`
LEFT JOIN `newsletter_post` ON `posts`.`id` = `newsletter_post`.`post_id`
LEFT JOIN `newsletters` ON `newsletters`.`id` = `newsletter_post`.`newsletter_id`
WHERE `feed_id` IN (6, 7) ORDER BY `posts`.`published` DESC LIMIT 40 OFFSET 0

но проблема в том, что это ужасно и не оптимизировано. Моя таблица posts содержит до 50 000 строк каждый месяц, и каждая строка содержит в среднем 3–10 КБ данных, поэтому иногда, когда я пытаюсь выполнить запрос (который часто запускается администратором для создания информационного бюллетеня, разбивки на страницы и т. д.), mysql показывает эту ошибку: слишком много строк для объединения и т. д., и в большинстве случаев это очень медленно.

и причина, по которой я делаю все это в одном запросе, заключается в том, что я хочу, чтобы результат был в одном ответе json, чтобы я мог быстро показать им пользователя, не делая дополнительных запросов.

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


person Sallar    schedule 03.01.2013    source источник
comment
Пожалуйста, вставьте план объяснения   -  person Sashi Kant    schedule 03.01.2013
comment
@SashiKant это план объяснения: d.pr/i/UKgh (таблица сообщений содержит ~2500 строк прямо сейчас)   -  person Sallar    schedule 03.01.2013


Ответы (4)


проиндексировать таблицу сообщений на

( feed_id , опубликовано )

поэтому данные уже оптимизированы для вашего предложения WHERE и предварительно отсортированы, чтобы помочь вашему ORDER BY.

person DRapp    schedule 03.01.2013
comment
Просто обычные индексы, подобные упомянутым Саши выше, для других таблиц? - person Sallar; 03.01.2013
comment
да, индексы из ваших таблиц также должны основываться на критериях/порядке, которые вы ожидаете получить в обычном режиме... В ДОПОЛНЕНИЕ к тем, которые требуют присоединения к pk/fk таблиц. Но даже те, которые относятся к другим таблицам, могут быть несколькими индексами столбцов, если вы присоединяетесь к примеру: TableA.SomeKey = TableB.ForeignSomeKey AND TableB.OtherField='SomeStatus'... Ваша таблицаB выиграет от индекса на ( ForeignSomeKey, OtherField ) для присоединиться. - person DRapp; 03.01.2013
comment
спасибо, еще один вопрос, что делать, если мне нужно отсортировать по чему-то другому, кроме published? как имя_источника? потому что у админа есть выбор типа сортировки - person Sallar; 03.01.2013
comment
+1, правильно. Индексация столбцов WHERE/ORDER BY повысит производительность. - person Afshin Mehrabani; 03.01.2013
comment
@SallarKaboli, затем подготовьте другой индекс по критериям/порядку, который вы будете использовать. Механизм ДОЛЖЕН выбрать индекс, наиболее подходящий для запроса. - person DRapp; 03.01.2013

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

1) КАК Саллар Каболи сказал вам, вы должны индексировать свои таблицы в столбцах, которые используются в запросах JOIN. Например:

      CREATE INDEX index1 ON newsletter_post (post_id);

2) ИСПОЛЬЗУЙТЕ только важные столбцы для JOINS.

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

Надеюсь, это будет полезно.

person Cesar    schedule 03.01.2013
comment
Да, это было бы очень полезно для вашего случая. Во-первых, вы должны проанализировать стоимость изменений и обучения. Многие люди считают, что MongoDB — это решение всех проблем, но это не так. Но я думаю, что это может быть полезно для вашего конкретного случая. - person Cesar; 03.01.2013

Чтобы завершить другие ответы, я предлагаю изменить эти типы в таблице posts:

1) Измените feed_id на int(4). У вас действительно больше int(4) фидов?
2) Измените is_unread на bit вместо int(1). Я должен сказать, что это может не улучшить ваш данный запрос в вопросе, но, согласно имени поля, правильный тип - bit.

Еще одно улучшение этого ответа заключается в том, что никогда не используйте значение по умолчанию int(11) для числовых полей или полей идентификатора, назначайте более конкретные типы. Использование типов меньшего размера также улучшит ваши индексы. Я не думаю, что вам нужно больше, чем int(4) для идентификаторов полей.

Например, индексирование и запрос столбца int(3) выполняется быстрее, чем int(11).

person Afshin Mehrabani    schedule 03.01.2013
comment
Спасибо, Афшин, я никогда не использовал bit, поэтому не знал, что он существует :) - person Sallar; 03.01.2013
comment
Этот ответ содержит некоторую дезинформацию о целочисленном типе данных. Число в скобках не влияет на количество байтов, используемых для его хранения. Вы должны использовать соответствующий целочисленный тип, например. INT, SMALLINT, TINYINT и т. д., если вы хотите уменьшить количество байтов, используемых для хранения значения. См. stackoverflow.com/a/4055587/1220706. - person antonagestam; 14.08.2017

Пожалуйста, создайте следующие индексы indexes on ::

1) `post_id` in `category_post`
2) `post_id` in `newsletter_post`
person Sashi Kant    schedule 03.01.2013
comment
Я добавил индексы, и запрос стал намного быстрее, чем раньше: 0,1 против 0,9 с. Нужно ли мне добавлять индексы к feed_id и published в posts, потому что я использую эти столбцы в WHERE и ORDER BY? - person Sallar; 03.01.2013
comment
@SallarKaboli: Посмотрите, это были столбцы, которые позволяли механизму БД сканировать все строки. Их индексы имели большой приоритет. Что касается индекса в столбцах, которые вы упоминаете, вы можете вставить обновленный план объяснения, чтобы я мог проверить можно ли его снова оптимизировать - person Sashi Kant; 03.01.2013
comment
Да надо еще добавить индекс по feed_id, тогда будет намного оптимальнее - person Sashi Kant; 03.01.2013