MySql медленно работает с join - как его ускорить - PullRequest
0 голосов
/ 01 февраля 2019

Мне нужно экспортировать 554 тыс. Записей из нашей базы данных mysql.По текущему курсу экспорт данных займет 5 дней, и медлительность в основном вызвана запросом, приведенным ниже.Структура данных состоит из

Companies
--Contacts
----(Contact)Activities

. Для контактов у нас есть индекс company_id.В таблице действий у нас есть индексы для contact_id и company_id, которые сопоставляются с соответствующими таблицами контактов и компаний.

Мне нужно захватить каждый контакт и самую последнюю дату его действия.Это запрос, который я выполняю, и его выполнение занимает около 0,5 секунды.

Select * 
from contacts 
left outer join (select  occurred_at
                        ,contact_id 
                 from activities 
                 where occurred_at is not null 
                 group by contact_id 
                 order by occurred_at desc) activities 
on contacts.id = activities.contact_id 
where company_id = 20

Если я удаляю объединение и просто выбираю * из контактов, где company_id = 20, запрос выполняется за 0,016 секунды..

Если я использую объяснение для получения информации о запросе на соединение, я получаю это enter image description here

Любые идеи о том, как я могу ускорить это?

Редактировать: вот определения таблиц.

CREATE TABLE `companies` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `street_address` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `city` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `state` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `county` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `website` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `external_id` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `falloff_date` date DEFAULT NULL,
  `zipcode` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `phone` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `company_id` int(11) DEFAULT NULL,
  `order_count` int(11) NOT NULL DEFAULT '0',
  `active_job_count` int(11) NOT NULL DEFAULT '0',
  `duplicate_of` int(11) DEFAULT NULL,
  `warm_date` datetime DEFAULT NULL,
  `employee_size` int(11) DEFAULT NULL,
  `dup_checked` tinyint(1) DEFAULT '0',
  `rating` int(11) DEFAULT NULL,
  `delinquent` tinyint(1) DEFAULT '0',
  `cconly` tinyint(1) DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `index_companies_on_name` (`name`),
  KEY `index_companies_on_user_id` (`user_id`),
  KEY `index_companies_on_company_id` (`company_id`),
  KEY `index_companies_on_external_id` (`external_id`),
  KEY `index_companies_on_state_and_dup_checked` (`id`,`state`,`dup_checked`,`duplicate_of`),
  KEY `index_companies_on_dup_checked` (`id`,`dup_checked`),
  KEY `index_companies_on_dup_checked_name` (`dup_checked`,`name`),
  KEY `index_companies_on_county` (`county`,`state`)
) ENGINE=InnoDB AUTO_INCREMENT=15190300 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


CREATE TABLE `contacts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `first_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `last_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `phone` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `extension` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `fax` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `active` tinyint(1) DEFAULT NULL,
  `main` tinyint(1) DEFAULT NULL,
  `company_id` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `external_id` int(11) DEFAULT NULL,
  `second_phone` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_contacts_on_company_id` (`company_id`),
  KEY `index_contacts_on_first_name` (`first_name`),
  KEY `index_contacts_on_last_name` (`last_name`),
  KEY `index_contacts_on_phone` (`phone`),
  KEY `index_contacts_on_email` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=11241088 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


CREATE TABLE `activities` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `kind` int(11) DEFAULT NULL,
  `contact_id` int(11) DEFAULT NULL,
  `call_status` int(11) DEFAULT NULL,
  `occurred_at` datetime DEFAULT NULL,
  `notes` text COLLATE utf8_unicode_ci,
  `user_id` int(11) DEFAULT NULL,
  `scheduled_for` datetime DEFAULT NULL,
  `priority` tinyint(1) DEFAULT NULL,
  `company_id` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `from_user_id` int(11) DEFAULT NULL,
  `to_user_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_activities_on_contact_id` (`contact_id`),
  KEY `index_activities_on_user_id` (`user_id`),
  KEY `index_activities_on_company_id` (`company_id`)
) ENGINE=InnoDB AUTO_INCREMENT=515340 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Ответы [ 3 ]

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

Это запрос , который часто возникает при переполнении стека.

Вот решение, использующее оконную функцию MySQL 8.0:

WITH latest_activities AS (
  SELECT contact_id, occurred_at,
    ROW_NUMBER() OVER (PARTITION BY contact_id ORDER BY occurred_at DESC) AS rn
  FROM activities
)
SELECT *
FROM contacts AS c
LEFT OUTER JOIN latest_activities 
  ON c.id = latest_activities.contact_id AND latest_activities.rn = 1
WHERE c.company_id = 20

Вот решение, которое должно работать на версиях до 8.0:

SELECT c.*, a.*
FROM contacts AS c
LEFT OUTER JOIN activities AS a ON a.contact_id = c.id
LEFT OUTER JOIN activities AS a2 ON a2.contact_id = c.id 
  AND a2.occurred_at > a.occurred_at
WHERE c.company_id = 20
  AND a2.contact_id IS NULL;

Другое решение:

SELECT c.*, a.*
FROM contacts AS c
LEFT OUTER JOIN activities AS a ON a.contact_id = c.id
LEFT OUTER JOIN (
  SELECT c2.contact_id, MAX(a2.occurred_at) AS occurred_at
  FROM activities AS a2
  INNER JOIN contacts AS c2 ON a2.contact_id = c2.id
  WHERE c2.company_id = 20 
  GROUP BY c2.contact_id ORDER BY NULL
) AS latest_activities
  ON latest_activities.contact_id = c.id
  AND latest_activities.occurred_at = a.occurred_at
WHERE c.company_id = 20

Было бы полезно создать новый индекс для действий (contact_id, произошло_at).

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

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

SELECT c.*
   , MAX(a.occurred_at) AS occurred_at
FROM contacts AS c
LEFT JOIN activities AS a
   ON c.id = a.contact_id AND a.occurred_at IS NOT NULL
WHERE c.company_id = 20
GROUP BY c.id;

Примечания: (1) это предполагает, что вы на самом деле этого не делалихотите, чтобы дубликат contact_id из вашего исходного подзапроса был в окончательных результатах.(2) Это также предполагает, что ваш сервер не настроен на требование полной группы;если это так, вам нужно будет вручную развернуть c.* в полный список столбцов и скопировать этот список в предложение GROUP BY.


Расширение комментариев dnoeth к вашему вопросу;если вы не запрашиваете каждую компанию отдельно по определенной причине ( разделение на части для загрузки, обработка структуры кода также обрабатывает другие компании по компаниям, что угодно) , вы можете настроить приведенный выше запрос следующим образом, чтобы получить все своирезультаты в одном запросе.

SELECT con.*
   , MAX(a.occurred_at) AS occurred_at
FROM companies AS com 
INNER JOIN contacts AS con ON com.id = con.company_id
LEFT JOIN activities AS a
   ON con.id = a.contact_id AND a.occurred_at IS NOT NULL
WHERE [criteria for companies chosen to be queried]
GROUP BY con.id
ORDER BY con.company_id, con.id
;
0 голосов
/ 01 февраля 2019

Не используйте подзапросы в предложении FROM, если можете помочь.Они мешают оптимизатору MySQL.Итак, если вам нужна одна строка:

Select c.*, a.occurred_at
from contacts c left outer join
     from activities a
     on c.id = a.contact_id and
        a.occurred_at is not null 
where c.company_id = 20
order by a.occurred_at desc
limit 1;

Если вы хотите одну строку на contact_id:

Select c.*, a.occurred_at
from contacts c left outer join
     from activities a
     on c.id = a.contact_id and
        a.occurred_at is not null and
        a.occurred_at = (select max(a2.occurred_at)
                         from activities a2
                         where a2.contact_id = a.contact_id
                        )
where c.company_id = 20         
order by a.occurred_at desc
limit 1;

Это может использовать индекс для activities(contact_id, occured_at)contact(company_id, contact_id).

Ваш запрос выполняет одно явное «нет-нет» и больше не поддерживается настройками по умолчанию в самых последних версиях MySQL.У вас есть неагрегированные столбцы в select, которых нет в group by.contact_id должен выдавать ошибку.

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