#mysql #sql #optimization #query-optimization
#mysql #sql #оптимизация #запрос-оптимизация
Вопрос:
У меня есть 6 таблиц:
CREATE TABLE IF NOT EXISTS `sbpr_groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`active` tinyint(1) DEFAULT '0',
`dnd` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=32 ;
CREATE TABLE IF NOT EXISTS `sbpr_newsletter` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`from` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`mail` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`subject` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`body` text COLLATE utf8_unicode_ci,
`attach1` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`attach2` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`attach3` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=14;
CREATE TABLE IF NOT EXISTS `sbpr_news_groups` (
`newsletter_id` int(11) NOT NULL,
`groups` int(11) NOT NULL,
KEY `fk_sbpr_news_groups_sbpr_newsletter1` (`newsletter_id`),
KEY `fk_sbpr_news_groups_sbpr_groups1` (`groups`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `sbpr_news_recs` (
`newsletter_id` int(11) NOT NULL,
`recipients` int(11) NOT NULL,
KEY `fk_sbpr_news_recs_sbpr_newsletter1` (`newsletter_id`),
KEY `fk_sbpr_news_recs_sbpr_recipients1` (`recipients`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `sbpr_recipients` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`mail` varchar(160) DEFAULT NULL,
`date_reg` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`active` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3008 ;
CREATE TABLE IF NOT EXISTS `sbpr_rec_groups` (
`rec_id` int(11) NOT NULL,
`group` int(11) NOT NULL,
KEY `fk_sbpr_rec_groups_sbpr_recipients` (`rec_id`),
KEY `fk_sbpr_rec_groups_sbpr_groups1` (`group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
С помощью этих внешних ключей:
ALTER TABLE `sbpr_news_groups`
ADD CONSTRAINT `fk_sbpr_news_groups_sbpr_groups1`
FOREIGN KEY (`groups`) REFERENCES `sbpr_groups` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION,
ADD CONSTRAINT `fk_sbpr_news_groups_sbpr_newsletter1`
FOREIGN KEY (`newsletter_id`) REFERENCES `sbpr_newsletter` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION;
ALTER TABLE `sbpr_news_recs`
ADD CONSTRAINT `fk_sbpr_news_recs_sbpr_newsletter1`
FOREIGN KEY (`newsletter_id`) REFERENCES `sbpr_newsletter` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION,
ADD CONSTRAINT `fk_sbpr_news_recs_sbpr_recipients1`
FOREIGN KEY (`recipients`) REFERENCES `sbpr_recipients` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION;
ALTER TABLE `sbpr_rec_groups`
ADD CONSTRAINT `fk_sbpr_rec_groups_sbpr_groups1`
FOREIGN KEY (`group`) REFERENCES `sbpr_groups` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION,
ADD CONSTRAINT `fk_sbpr_rec_groups_sbpr_recipients`
FOREIGN KEY (`rec_id`) REFERENCES `sbpr_recipients` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION;
Визуальная структура таблиц:
Я хочу выбрать все строки из таблицы sbpr_newsletter и добавить к каждой из этих строк количество строк из sbpr_recipients, идентификатор которых, указанный в sbpr_news_recs или предписанный в sbpr_rec_groups, зависит от FKS.
Пример. Я хочу выбрать количество всех получателей текущего информационного бюллетеня, которые находятся в sbpr_news_recs или существуют в группе, которая находится в sbpr_rec_groups, плюс количество активных получателей.
У меня есть рабочий SQL:
SELECT d.id, d.subject , d.created_date,
(SELECT count(*) FROM sbpr_recipients r
LEFT JOIN sbpr_news_recs nr ON nr.recipients = r.id
LEFT JOIN sbpr_rec_groups g ON g.rec_id = r.id
LEFT JOIN sbpr_news_groups ng ON ng.groups = g.group
WHERE nr.newsletter_id = d.id OR ng.newsletter_id = d.id) AS repicients,
(SELECT count(*) FROM sbpr_recipients r
LEFT JOIN sbpr_news_recs nr ON nr.recipients = r.id
LEFT JOIN sbpr_rec_groups g ON g.rec_id = r.id
LEFT JOIN sbpr_news_groups ng ON ng.groups = g.group
WHERE (nr.newsletter_id = d.id OR ng.newsletter_id = d.id)
AND r.active = 1) AS active_repicients
FROM sbpr_newsletter d
ORDER BY d.id ASC, d.id
Объяснение этого sql:
Вопрос: Как я могу оптимизировать свой sql?
Комментарии:
1. 1 за все подробности. Хотелось бы, чтобы больше вопросов было похоже на это.
2. Как выглядит ваше объяснение при изменении
order by d.id ASC, d.id
наorder by d.id ASC
?3. @ArtStudio Это выглядит хорошо: «сортировка файлов» исчезла. Насколько медленным сейчас является ваш запрос? Можете ли вы повторить это с помощью
EXPLAIN EXTENDED ...
, если ваша версия MySQL поддерживает это.4. @eisberg те же 3 строки, полученные за 0:00.1040 сек, ОБЪЯСНЯЮТ РАСШИРЕННО: imm.io/4YVt
5. то же самое с комментарием от Wes — отличный вопрос с отличной информацией. Хорошо объясненная проблема — это наполовину решенная проблема.
Ответ №1:
Просто подходите к оптимизации, два запроса SELECT передаются в предложение JOIN —
SELECT d.id
, d.subject
, d.created_date
, count(if(nr_newsletter_id is not null or ng_newsletter_id is not null, 1, null)) repicients
, count(if((nr_newsletter_id is not null or ng_newsletter_id is not null) and t.active = 1, 1, null)) active_repicients
FROM
sbpr_newsletter d
LEFT JOIN (
SELECT nr.newsletter_id nr_newsletter_id
, ng.newsletter_id ng_newsletter_id
, r.active
FROM
sbpr_recipients r
LEFT JOIN sbpr_news_recs nr
ON nr.recipients = r.id
LEFT JOIN sbpr_rec_groups g
ON g.rec_id = r.id
LEFT JOIN sbpr_news_groups ng
ON ng.groups = g.group
) t
ON nr_newsletter_id = d.id OR ng_newsletter_id = d.id
GROUP BY
d.id;
Я немного переписал ваш запрос, он не тестировался, но попробуйте.
Комментарии:
1. Очень приятно! Он выполняется в два раза быстрее, чем мой вариант.
2. Я только что заметил, что результаты вашего запроса и моего отличаются: imm.io/4YWf
3. Я думаю, что некоторые условия следует добавить в функции подсчета. Строки следует подсчитывать только тогда, когда nr_newsletter_id не равен нулю или ng_newsletter_id не равен нулю.
Ответ №2:
Вы МОГЛИ бы создать представление и запрос, которые вместо этого — компромисс — это хранилище, но должны значительно снизить нагрузку на сервер…
Ответ №3:
Подзапрос для получателей / active_recipients выполняется дважды, и каждый раз возвращает 3311 записей, поэтому его стоило бы определить как представление.
В противном случае определите индексы для внешних ключей, которые вы используете в объединениях.
Комментарии:
1. Запрос от @Devart выполняется одновременно с индексами и без них, нужно ли мне все еще добавлять индексы?