Django медленное внутреннее соединение в таблице с более чем 10 миллионами записей
Я использую mysql с Django. Я пытаюсь подсчитать количество visitor_pages для определенного дилера за определенный промежуток времени. Я хотел бы поделиться необработанным sql-запросом, который я получил из панели инструментов отладки django.
SELECT COUNT(*) AS `__count`
FROM `visitor_page`
INNER JOIN `dealer_visitors`
ON (`visitor_page`.`dealer_visitor_id` = `dealer_visitors`.`id`)
WHERE (`visitor_page`.`date_time` BETWEEN '2021-02-01 05:51:00' AND '2021-03-21 05:50:00' AND `dealer_visitors`.`dealer_id` = 15)
Проблема в том, что у меня более 13 миллионов записей в таблице visitor_pages и около 1,5 миллиона записей в таблице dealer_visitor. Я уже проиндексировал date_time. Я думаю использовать материализованное представление, но прежде чем приступить к этому, я был бы очень признателен за предложения о том, как я могу улучшить этот запрос.
visitor_pages schema:
CREATE TABLE `visitor_page` (
`id` int NOT NULL AUTO_INCREMENT,
`date_time` datetime(6) DEFAULT NULL,
`added_at` datetime(6) DEFAULT NULL,
`updated_at` datetime(6) DEFAULT NULL,
`page_id` int NOT NULL,
`dealer_visitor_id` int NOT NULL,
PRIMARY KEY (`id`),
KEY `visitor_page_page_id_246babdf_fk_web_page_id` (`page_id`),
KEY `visitor_page_dealer_visitor_id_e2dddea2_fk_dealer_visitors_id` (`dealer_visitor_id`),
KEY `visitor_page_date_time_06e9e9f5` (`date_time`),
CONSTRAINT `visitor_page_dealer_visitor_id_e2dddea2_fk_dealer_visitors_id` FOREIGN KEY (`dealer_visitor_id`) REFERENCES `dealer_visitors` (`id`),
CONSTRAINT `visitor_page_page_id_246babdf_fk_web_page_id` FOREIGN KEY (`page_id`) REFERENCES `web_page` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13626649 DEFAULT CHARSET=latin1;
dealer_visitors schema:
CREATE TABLE `dealer_visitors` (
`id` int NOT NULL AUTO_INCREMENT,
`visit_date` datetime(6) DEFAULT NULL,
`added_at` datetime(6) DEFAULT NULL,
`updated_at` datetime(6) DEFAULT NULL,
`dealer_id` int NOT NULL,
`visitor_id` int NOT NULL,
`type` int DEFAULT NULL,
`notes` longtext,
`location` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `dealer_visitors_dealer_id_306e2202_fk_dealer_id` (`dealer_id`),
KEY `dealer_visitors_visitor_id_27ae498e_fk_visitor_id` (`visitor_id`),
KEY `dealer_visitors_type_af0f7d79` (`type`),
KEY `dealer_visitors_visit_date_f2b138c9` (`visit_date`),
CONSTRAINT `dealer_visitors_dealer_id_306e2202_fk_dealer_id` FOREIGN KEY (`dealer_id`) REFERENCES `dealer` (`id`),
CONSTRAINT `dealer_visitors_visitor_id_27ae498e_fk_visitor_id` FOREIGN KEY (`visitor_id`) REFERENCES `visitor` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1524478 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
EXPLAIN ANALYZE запрос дает мне следующее:
Для этого запроса:
SELECT COUNT(*) AS `__count`
FROM visitor_page vp JOIN
dealer_visitors dv
ON vp.dealer_visitor_id = dv.id
WHERE vp.date_time BETWEEN '2021-02-01 05:51:00' AND '2021-03-21 05:50:00' AND
dv.dealer_id = 15;
Лучшие индексы находятся на dealer_visitors(dealer_id, date_time, id)
и visitor_page(dealer_visitor_id)
.
Индекс только на date
немного помогает. Но вы извлекаете данные за месяц, и это может быть очень много данных для обработки. Наличие dealer_id
в качестве первого столбца в индексе ограничит данные только строками для этого дилера за этот период времени.
В зависимости от распределения данных, оптимизатор может выбрать одну из таблиц для начала или другую. Итак, представим оптимальные индексы для каждого случая:
ON `visitor_page`.`dealer_visitor_id` = `dealer_visitors`.`id`
WHERE `visitor_page`.`date_time` BETWEEN ...
AND `dealer_visitors`.`dealer_id` = 15
Начиная с visitor_page
:
visitor_page: INDEX(date_time) -- (already exists)
dealer_visitors: (already has PRIMARY KEY(id))
Начиная с dealer_visitors
:
dealer_visitors: INDEX(dealer_id) -- (already exists)
visitor_page: INDEX(dealer_visitor_id, date_time) -- in this order
и исключить dealer_visitors_visitor_id_27ae498e_fk_visitor_id
как излишний.
В чистом виде добавляется один индекс и отбрасывается один индекс.
Материализованное представление -- Часто для отчетов Хранилища данных лучше всего создавать и постепенно поддерживать "сводную таблицу" ("материализованное представление"). Очень странный диапазон дат (1 месяц + 20 дней - 61 секунда) делает это неудобным. Обычно удобно делать таблицу на основе целых дней. Если вы можете перейти на ежедневный (или ежечасный), то см. http://mysql.rjweb.org/doc.php/summarytables
Еще кое-что нужно проверить: Сколько у вас оперативной памяти? Что говорит SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
?
Я вижу, что таблицы имеют разную кодировку/колляцию. Это не является проблемой для данного запроса, но если у вас есть другие запросы, которые JOIN
на VARCHARs
, проверьте, что они используют одинаковый collation.