SELECT COUNT с оптимизацией JOIN для таблиц с> 100M строками - PullRequest
0 голосов
/ 05 февраля 2019

У меня следующий запрос

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, 
       COUNT(1) AS sclr_1 
FROM applications a0_ INNER JOIN 
     package_codes p1_ ON a0_.id = p1_.application_id 
WHERE a0_.created_date BETWEEN '2019-01-01' AND '2020-01-01' AND
      p1_.type = 'Package 1'
GROUP BY sclr_0

--- EDIT ---

Большинство из вас сосредоточено на GROUP BY и SUBSTRING, но это не является причиной проблемы.

Следующий запрос имеет одинаковое время выполнения:

SELECT COUNT(1) AS sclr_1 
FROM applications a0_ INNER JOIN 
     package_codes p1_ ON a0_.id = p1_.application_id 
WHERE a0_.created_date BETWEEN '2019-01-01' AND '2020-01-01' AND
      p1_.type = 'Package 1'

--- EDIT 2 ---

После добавления индекса на application.created_date и принудительного использования запроса для указанногоИндексы @DDS предполагают, что время выполнения падает до ~ 750 мс

Текущий запрос выглядит следующим образом:

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, 
       COUNT(1) AS sclr_1 
FROM applications a0_ USE INDEX (applications_created_date_idx) INNER JOIN 
     package_codes p1_ USE INDEX (PRIMARY, UNIQ_70A9C6AA3E030ACD, package_codes_type_idx) ON a0_.id = p1_.application_id 
WHERE a0_.created_date BETWEEN '2019-01-01' AND '2020-01-01' AND
      p1_.type = 'Package 1'
GROUP BY sclr_0

--- EDIT 3 ---

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

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, 
       COUNT(1) AS sclr_1 
FROM applications a0_ USE INDEX (applications_created_date_idx) INNER JOIN 
     package_codes p1_ USE INDEX (package_codes_application_idx) ON a0_.id = p1_.application_id 
WHERE a0_.created_date BETWEEN '2019-01-01' AND '2020-01-01' AND
      p1_.type = 'Package 1'
GROUP BY sclr_0

--- END EDIT ---

package_codes содержит более 100 000 000 записей.

приложения содержат более 250 000 записей.

Запрос занимает 2 минуты, чтобы получить результат.Есть ли способ оптимизировать его?Я застрял на MySQL 5.5.

Таблицы:

CREATE TABLE `applications` (
  `id` int(11) NOT NULL,
  `created_date` datetime NOT NULL,
  `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `surname` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE `applications`
  ADD PRIMARY KEY (`id`),
  ADD KEY `applications_created_date_idx` (`created_date`);

ALTER TABLE `applications`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
CREATE TABLE `package_codes` (
  `id` int(11) NOT NULL,
  `application_id` int(11) DEFAULT NULL,
  `created_date` datetime NOT NULL,
  `type` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  `code` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  `disabled` tinyint(1) NOT NULL DEFAULT '0',
  `meta_data` longtext COLLATE utf8mb4_unicode_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE `package_codes`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `UNIQ_70A9C6AA3E030ACD` (`application_id`),
  ADD KEY `package_codes_code_idx` (`code`),
  ADD KEY `package_codes_type_idx` (`type`),
  ADD KEY `package_codes_application_idx` (`application_id`),
  ADD KEY `package_codes_code_application_idx` (`code`,`application_id`);

ALTER TABLE `package_codes`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

ALTER TABLE `package_codes`
  ADD CONSTRAINT `FK_70A9C6AA3E030ACD` FOREIGN KEY (`application_id`) REFERENCES `applications` (`id`);

Ответы [ 4 ]

0 голосов
/ 06 февраля 2019

Оптимальные индексы:

p1_:  (type, application_id)
a0_:  (created_date, id)

Они применяются ко всем (?) Версиям представленного запроса, кроме тех, которые "форсируют" индекс.

Оптимизатор попытается решить, следует линачать с p1_ или a0_.И с этими индексами у него должен быть хороший шанс выбрать лучший стол.

SUBSTRING(a0_.created_date FROM 1 FOR 10) можно упростить до DATE(a0_.created_date), но я сомневаюсь, что это изменит производительность.

Обратите внимание, что индексы будут «покрывать», тем самым давая дополнительный импульс.EXPLAIN указывает на это, говоря Using index (не Using index condition).

Дальнейшее улучшение: избавьтесь от package_codes.id и продвигайте application_id, чтобы стать PRIMARY KEY.Это может привести к упрощению запроса!

Мой совет относится (возможно) ко всем версиям MySQL.

0 голосов
/ 05 февраля 2019

Вам необходимо создать составной индекс.Похоже, что вы создали отдельные индексы для таблицы.В этом случае вам нужен отдельный индекс для созданного_даты в package_codes, а также составной индекс для созданного_даты и типа.

Возможно приведение даты до и группирование после.

0 голосов
/ 05 февраля 2019

После добавления индекса в application.created_date и принудительного использования запроса для использования указанных индексов, поскольку @DDS предлагает сократить время выполнения до ~ 750 мс

Окончательный запрос должен выглядеть следующим образом:

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, 
       COUNT(1) AS sclr_1 
FROM applications a0_ USE INDEX (applications_created_date_idx) INNER JOIN 
     package_codes p1_ USE INDEX (package_codes_application_idx) ON a0_.id = p1_.application_id 
WHERE a0_.created_date BETWEEN '2019-01-01' AND '2020-01-01' AND
      p1_.type = 'Package 1'
GROUP BY sclr_0
0 голосов
/ 05 февраля 2019

Мое предложение состоит в том, чтобы избежать этого:

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, 
[...]  
GROUP BY sclr_0

, потому что каждый раз, когда БД пересчитывает поле и не может использовать индексы для него, если вы поместите эти данные в его собственный столбец и сделаетеиндекс, по которому ваша производительность должна улучшиться

или, по крайней мере, использовать функцию date_part, чтобы mysql смог использовать ее индексацию (очевидно, вы должны добавить индекс для application.created_date)

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, COUNT(1) AS sclr_1 
FROM applications a0_ INNER JOIN 
     package_codes p1_ ON (a0_.id = p1_.application_id and a0_.created_date 
BETWEEN '2019-01-01' AND '2020-01-01' and p1_.type = 'Package 1')      
FORCE INDEX (date_index, type_index)
Group by date(a0_.created_date)

другая оптимизация состоит в том, чтобы «выдвинуть» условия в предложение «on», чтобы mysql «фильтровал» данные перед объединением -> объединение выполняется по гораздо меньшим строкам

EDIT: это создание индекса дляdate

CREATE INDEX date_index ON application(created_date);

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

CREATE INDEX type_index ON package_codes(type);

[EDIT 2], пожалуйста, опубликуйте результат

select count(distinct date(a0_.created_date)) as N_DATES, count(distinct type)as N_TYPES
FROM applications a0_ INNER JOIN 
     package_codes p1_ ON a0_.id = p1_.application_id 

Просто иметь представление об индексе ведьмы будет более избирательно

Полезно ссылка для оптимизации индекса с использованием MySQL

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