Оптимизируйте полное внешнее соединение MySQL для большого количества данных

У нас есть следующие таблицы mysql (упрощено для перехода прямо к делу)

CREATE TABLE `MONTH_RAW_EVENTS` (
  `idEvent` int(11) unsigned NOT NULL,
  `city` varchar(45) NOT NULL,
  `country` varchar(45) NOT NULL,
  `ts` datetime NOT NULL,
  `idClient` varchar(45) NOT NULL,
  `event_category` varchar(45) NOT NULL,
  ... bunch of other fields
  PRIMARY KEY (`idEvent`),
  KEY `idx_city` (`city`),
  KEY `idx_country` (`country`),
  KEY `idClient` (`idClient`),
) ENGINE=InnoDB;

CREATE TABLE `compilation_table` (
  `idClient` int(11) unsigned DEFAULT NULL,
  `city` varchar(200) DEFAULT NULL,
  `month` int(2) DEFAULT NULL,
  `year` int(4) DEFAULT NULL,
  `events_profile` int(10) unsigned NOT NULL DEFAULT '0',
  `events_others` int(10) unsigned NOT NULL DEFAULT '0',
  `events_total` int(10) unsigned NOT NULL DEFAULT '0',
  KEY `idx_month` (`month`),
  KEY `idx_year` (`year`),
  KEY `idx_idClient` (`idClient`),
  KEY `idx_city` (`city`)
) ENGINE=InnoDB;

MONTH_RAW_EVENTS содержит почти 20 миллионов строк, в которых указаны действия пользователя на веб-сайте, размер почти 4 ГБ

compilation_table имеет сводку клиентов / городов за каждый месяц, мы используем ее для отображения статистики на веб-сайте в режиме реального времени

Мы обрабатываем статистику (от первой таблицы до второй) один раз в месяц, и мы пытаемся оптимизировать запрос, который выполняет такую ​​операцию (поскольку до сих пор мы обрабатываем все в PHP, которое занимает много времени)

Вот запрос, который мы придумали, который, кажется, выполняет свою работу при использовании небольших подмножеств данных, проблема, которая занимает более 6 часов, чтобы обработать полный набор данных

INSERT INTO compilation_table (idClient,city,month,year,events_profile,events_others)


    SELECT  IFNULL(OTHERS.idClient,AP.idClient) as idClient,
            IF(IFNULL(OTHERS.city,AP.city)='','Others',IFNULL(OTHERS.city,AP.city)) as city,
        01,2014,
    IFNULL(AP.cnt,0) as events_profile,
        IFNULL(OTHERS.cnt,0) as events_others           

    FROM
    (
        SELECT idClient,CONCAT(city,', ',country) as city,count(*) as cnt 
        FROM `MONTH_RAW_EVENTS` WHERE `ts`>'2014-01-01 00:00:00' AND `ts`<='2014-01-31 23:59:59'
        AND `event_category`!='CLIENT PROFILE'
        GROUP BY idClient,city
    ) as OTHERS
 LEFT JOIN 
    (
        SELECT idClient,CONCAT(city,', ',country) as city,count(*) as cnt 
        FROM `MONTH_RAW_EVENTS` WHERE `ts`>'2014-01-01 00:00:00' AND `ts`<='2014-01-31 23:59:59'
        AND `event_category`='CLIENT PROFILE'
        GROUP BY idClient,city
    ) as CLIPROFILE 
    ON CLIPROFILE.city=OTHERS.city and CLIPROFILE.idClient=OTHERS.idClient

 UNION

    SELECT  IFNULL(OTHERS.idClient,CLIPROFILE.idClient) as idClient,
            IF(IFNULL(OTHERS.city,CLIPROFILE.city)='','Others',IFNULL(OTHERS.city,CLIPROFILE.city)) as city,
            01,2014,
            IFNULL(CLIPROFILE.cnt,0) as events_profile,
            IFNULL(OTHERS.cnt,0) as events_others           
    FROM
    (
        SELECT idClient,CONCAT(city,', ',country) as city,count(*) as cnt 
        FROM `MONTH_RAW_EVENTS` WHERE `ts`>'2014-01-01 00:00:00' AND `ts`<='2014-01-31 23:59:59'
        AND `event_category`!='CLIENT PROFILE'
        GROUP BY idClient,city
    ) as OTHERS
 RIGHT JOIN 
    (
        SELECT idClient,CONCAT(city,', ',country) as city,count(*) as cnt 
        FROM `MONTH_RAW_EVENTS` WHERE `ts`>'2014-01-01 00:00:00' AND `ts`<='2014-01-31 23:59:59'
        AND `event_category`='CLIENT PROFILE'
        GROUP BY idClient,city
    ) as CLIPROFILE 
    ON CLIPROFILE.city=OTHERS.city and CLIPROFILE.idClient=OTHERS.idClient

То, что мы пытаемся сделать, это FULL Outer Join в Mysql, поэтому основная схема запроса выглядит так: предложенная здесь

Как мы можем оптимизировать запрос? мы пробовали разные индексы, обменивались вещами, но через 8 часов все еще не закончили работу,

Сервер MySQL - это выделенная машина Percona MySQL 5.5 с 2cpu, 2 Гб оперативной памяти и диском SSD, мы оптимизировали конфигурацию такого сервера с помощью инструментов Percona,

Любая помощь могла бы быть полезна,

Спасибо

1 ответ

Решение

Вы делаете UNION, который приводит к обработке DISTINCT.

Обычно лучше переписать полное объединение в левое объединение плюс несоответствующие строки правого объединения (если это правильно 1:n соединение)

OTHERS LEFT JOIN CLIPROFILE 
ON CLIPROFILE.city=OTHERS.city and CLIPROFILE.idClient=OTHERS.idClient
union all
OTHERS RIGHT JOIN CLIPROFILE 
ON CLIPROFILE.city=OTHERS.city and CLIPROFILE.idClient=OTHERS.idClient
WHERE OTHERS.idClient IS NULL 

Кроме того, вы можете материализовать результаты производных таблиц во временных таблицах до их присоединения, таким образом, вычисление выполняется только один раз (я не знаю, достаточно ли у умного оптимизатора MySQL для этого автоматически).

Кроме того, может быть более эффективно группировать и объединять город / страну в виде отдельных столбцов и делать КОНКАТ (город,',', страна) в качестве города на внешнем шаге.

Другие вопросы по тегам