2015-02-20 2 views
2

Мы используем MySql как наш DBMySql составной индекс

Следующий запрос выполняется в таблице mysql (около 25 миллионов записей). Я вставил два запроса здесь. Запросы выполняются слишком медленно, и мне было интересно, могут ли улучшенные составные индексы улучшить ситуацию.

Любая идея о том, какой лучший составной индекс будет?

и предложить мне такое композитный индекс требуется для этих запросов

ПЕРВЫХ ЗАПРОСОВ

EXPLAIN SELECT log_type, 
     count(DISTINCT subscriber_id) AS distinct_count, 
     count(*) as total_count 
FROM stats.campaign_logs 
WHERE domain = 'xxx' 
    AND campaign_id='12345' 
    AND log_type IN ('EMAIL_SENT', 'EMAIL_CLICKED', 'EMAIL_OPENED', 'UNSUBSCRIBED') 
    AND log_time BETWEEN CONVERT_TZ('2015-02-12 00:00:00','+05:30','+00:00') 
        AND CONVERT_TZ('2015-02-19 23:59:58','+05:30','+00:00') 
GROUP BY log_type 

EXPLAIN указанного запроса

+----+-------------+---------------+-------------+--------------------------------------------------------------+--------------------------------+---------+------+-------+------------------------------------------------------------------------------+ 
| id | select_type | table   | type  | possible_keys            | key       | key_len | ref | rows | Extra                  | 
+----+-------------+---------------+-------------+--------------------------------------------------------------+--------------------------------+---------+------+-------+------------------------------------------------------------------------------+ 
| 1 | SIMPLE  | campaign_logs | index_merge | campaign_id_index,domain_index,log_type_index,log_time_index | campaign_id_index,domain_index | 153,153 | NULL | 35683 | Using intersect(campaign_id_index,domain_index); Using where; Using filesort | 
+----+-------------+---------------+-------------+--------------------------------------------------------------+--------------------------------+---------+------+-------+------------------------------------------------------------------------------+ 

ВТОРОЙ QUERY

SELECT campaign_id 
    , subscriber_id 
    , campaign_name 
    , log_time 
    , log_type 
    , message 
    , UNIX_TIMESTAMP(log_time) AS time 
    FROM campaign_logs 
WHERE domain = 'xxx' 
    AND log_type = 'EMAIL_OPENED' 
ORDER 
    BY log_time DESC 
LIMIT 20; 

EXPLAIN из Бове запрос

+----+-------------+---------------+-------------+-----------------------------+-----------------------------+---------+------+--------+---------------------------------------------------------------------------+ 
| id | select_type | table   | type  | possible_keys    | key       | key_len | ref | rows | Extra                  | 
+----+-------------+---------------+-------------+-----------------------------+-----------------------------+---------+------+--------+---------------------------------------------------------------------------+ 
| 1 | SIMPLE  | campaign_logs | index_merge | domain_index,log_type_index | domain_index,log_type_index | 153,153 | NULL | 118392 | Using intersect(domain_index,log_type_index); Using where; Using filesort | 
+----+-------------+---------------+-------------+-----------------------------+-----------------------------+---------+------+--------+---------------------------------------------------------------------------+ 

ТРЕТИЙ QUERY

EXPLAIN SELECT *, UNIX_TIMESTAMP(log_time) AS time FROM stats.campaign_logs WHERE domain = 'xxx' AND log_type <> 'EMAIL_SLEEP' AND subscriber_id = '123' ORDER BY log_time DESC LIMIT 100 

EXPLAIN указанного запроса

+----+-------------+---------------+------+-------------------------------------------------+---------------------+---------+-------+------+-----------------------------+ 
| id | select_type | table   | type | possible_keys         | key     | key_len | ref | rows | Extra      | 
+----+-------------+---------------+------+-------------------------------------------------+---------------------+---------+-------+------+-----------------------------+ 
| 1 | SIMPLE  | campaign_logs | ref | subscriber_id_index,domain_index,log_type_index | subscriber_id_index | 153  | const | 35 | Using where; Using filesort | 
+----+-------------+---------------+------+-------------------------------------------------+---------------------+---------+-------+------+-----------------------------+ 

Если вы хотите любые другие детали я могу предоставить здесь

UPDATE (2016/Апрель/22): Теперь мы хотим добавить еще один столбец в существующую таблицу, которая является идентификатором узла. Одна кампания может иметь несколько узлов. Независимо от того, какие отчеты мы генерируем в кампаниях, нам также нужны эти отчеты об отдельных узлах.

, например

SELECT log_type, 
      count(DISTINCT subscriber_id) AS distinct_count, 
      count(*) as total_count 
    FROM stats.campaign_logs 
    WHERE domain = 'xxx', 
     AND campaign_id='12345', 
     AND node_id = '34567', 
     AND log_type IN ('EMAIL_SENT', 'EMAIL_CLICKED', 'EMAIL_OPENED', 'UNSUBSCRIBED') 
     AND log_time BETWEEN CONVERT_TZ('2015-02-12 00:00:00','+05:30','+00:00') 
         AND CONVERT_TZ('2015-02-19 23:59:58','+05:30','+00:00') 
    GROUP BY log_type 

CREATE TABLE `camp_logs` (
    `domain` varchar(50) DEFAULT NULL, 
    `campaign_id` varchar(50) DEFAULT NULL, 
    `subscriber_id` varchar(50) DEFAULT NULL, 
    `message` varchar(21000) DEFAULT NULL, 
    `log_time` datetime DEFAULT NULL, 
    `log_type` varchar(50) DEFAULT NULL, 
    `level` varchar(50) DEFAULT NULL, 
    `campaign_name` varchar(500) DEFAULT NULL, 
    KEY `subscriber_id_index` (`subscriber_id`), 
    KEY `log_type_index` (`log_type`), 
    KEY `log_time_index` (`log_time`), 
    KEY `campid_domain_logtype_logtime_subid_index` (`campaign_id`,`domain`,`log_type`,`log_time`,`subscriber_id`), 
    KEY `domain_logtype_logtime_index` (`domain`,`log_type`,`log_time`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 | 

Размер выпуска.

Поскольку у нас есть два составных индекса, индексный файл быстро растет. Ниже приведены текущие статистические данные таблицы. Размер данных: 30 ГБ Размер Индекс: 35 GB

для отчетов о node_id мы хотим обновить наш существующий сводный индекс

из

KEY `campid_domain_logtype_logtime_subid_index` (`campaign_id`,`domain`,`log_type`,`log_time`,`subscriber_id`), 

в

KEY `campid_domain_logtype_logtime_subid_nodeid_index` (`campaign_id`,`domain`,`log_type`,`log_time`,`subscriber_id`,`node_id`) 

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

Благодаря

+0

Какова функциональная разница между 'DATE ('2015-02-12 00:00:00') и DATE ('2015-02-19 23:59:58')' и ''2015-02-12 00 : 00: 00 'И' 2015-02-19 23: 59: 58''? – Strawberry

+0

Попробуйте упростить свои запросы. Первый имеет 'GROUP BY' как для внутреннего запроса, так и для внешнего запроса. Никакой индекс не поможет вам в этом. – axiac

ответ

2

Это ваш первый запрос:

SELECT A.log_type, count(*) as distinct_count, sum(A.total_count) as total_count 
from (SELECT log_type, count(subscriber_id) as total_count 
     FROM stats.campaign_logs 
     WHERE domain = 'xxx' AND campaign_id = '12345' AND 
      log_type IN ('EMAIL_SENT', 'EMAIL_CLICKED', 'EMAIL_OPENED', 'UNSUBSCRIBED') AND 
      DATE(CONVERT_TZ(log_time,'+00:00','+05:30')) BETWEEN DATE('2015-02-12 00:00:00') AND DATE('2015-02-19 23:59:58') 
     GROUP BY subscriber_id,log_type) A 
GROUP BY A.log_type; 

Это лучше написано как:

 SELECT log_type, count(DISTINCT subscriber_id) as total_count 
     FROM stats.campaign_logs 
     WHERE domain = 'xxx' AND campaign_id = '12345' AND 
      log_type IN ('EMAIL_SENT', 'EMAIL_CLICKED', 'EMAIL_OPENED', 'UNSUBSCRIBED') AND 
      DATE(CONVERT_TZ(log_time, '+00:00', '+05:30')) BETWEEN DATE('2015-02-12 00:00:00') AND DATE('2015-02-19 23:59:58') 
     GROUP BY log_type; 

Лучший показатель по этому вопросу, вероятно: campaign_logs(domain, campaign_id, log_type, log_time, subscriber_id). Это индекс покрытия для запроса. Первые три клавиши должны использоваться для фильтрации where.

+0

Ваш переписанный lost_count, который _might_ будет 'COUNT (DISTINCT subscriber_id, log_type)'. Индекс покрытия выглядит неплохо. –

+0

@RickJames. , , Исходный запрос подсчитывает количество различных подписчиков для каждого типа журнала. Вот как работает вложенный «group by». –

+0

@Gordon Linoff Спасибо за ваш комментарий, я обновил свой первый запрос и вставил еще один запрос, всего 3 запроса, вы могли бы предложить лучшие комбинации составных индексов для всех запросов – Rams

0

Rewrite первый запрос, как показано ниже:

SELECT log_type, 
     count(DISTINCT subscriber_id) AS distinct_count, 
     count(*) as total_count 
FROM stats.campaign_logs 
WHERE domain = 'xxx' 
    AND campaign_id='12345' 
    AND log_type IN ('EMAIL_SENT', 'EMAIL_CLICKED', 'EMAIL_OPENED', 'UNSUBSCRIBED') 
    AND DATE(CONVERT_TZ(log_time,'+00:00','+05:30')) 
     BETWEEN DATE('2015-02-12 00:00:00') AND DATE('2015-02-19 23:59:58') 
GROUP BY log_type 

Он должен производить те же результаты, но это не имеет внутренних запросов и одну GROUP BY.

В таблице уже есть все необходимые индексы.

Последнее условие (DATE(...)) не может использовать какой-либо индекс, потому что ему нужно вычислить значение, используя log_time для каждой строки. Перепишите его, чтобы сравнить пустое значение log_time с некоторыми вычисленными значениями (примените CONVERT_TZ() к границам интервалов, выполняющих обратное преобразование).

Таким образом, он сравнивает индексированный столбец log_time против некоторых постоянных значений, используя полную мощность индексации:

AND log_time BETWEEN CONVERT_TZ('2015-02-12 00:00:00','+05:30','+00:00') 
        AND CONVERT_TZ('2015-02-19 23:59:58','+05:30','+00:00') 

индекс нескольких столбцов на колонках domain и log_type (в таком порядке) может помочь ускорить и (они оба имеют Using intersect(domain_index,log_type_index) в столбце Extra, возвращенном EXPLAIN).

Если вы создадите такой индекс, удалите индекс domain_index. Индекс по столбцам domain и log_type (в этом порядке) также может использоваться как индекс только для domain. MySQL может использовать его вместо domain_index. Наличие двух идентичных индексов делает операции записи медленнее и не имеет преимуществ.

+0

Спасибо за ваш комментарий, я обновил свой первый запрос и вставил еще один запроса, всего 3 запроса, вы могли бы предложить наилучшие комбинации составных индексов для всех запросов. Вышеуказанный третий запрос выполняется быстро. – Rams

0

Для запроса 1, индекс @Gordon Linoff превосходен (по крайней мере, после переписан SELECT):

INDEX(domain, campaign_id, log_type, log_time, subscriber_id) 
INDEX(campaign_id, domain, log_type, log_time, subscriber_id) -- equally good. 

По запросу 2: «index_merge» является признаком того, что вы могли бы, вероятно, извлечь выгоду из «индекса соединения ». Второй запрос лучше всего обрабатывается одним из следующих, который (я думаю) вычислит набор результатов только с 20 чтениями, а не 118K, как оценил EXPLAIN.

INDEX(domain, log_type, log_time) 
INDEX(log_type, domain, log_time) 

Имейте в виду, что при добавлении индексов вы должны избавиться от избыточных. Например, INDEX(domain, ...) делает KEY domain_index (domain) избыточным, поэтому последний может быть DROPped.

В целом, я бы рекомендовал

DROP INDEX(campaign_id_index), 
ADD INDEX(campaign_id, domain, log_type, log_time, subscriber_id), 
DROP INDEX(domain), 
ADD INDEX(domain, log_type, log_time) 
PRIMARY KEY(id, log_time) -- if you also add PARTITIONing; see below 

Другие рекомендации:

  • InnoDB должны иметь первичный ключ.(Для вас был предоставлен 6-байтовый скрытый.) Рекомендовать ADD COLUMN id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY.

  • Рассмотрите возможность изменения log_type от громоздкого VARCHAR до ENUM.

  • Если абонент_ид действительно является номером, тогда рассмотрите INT UNSIGNED.
  • Вам понадобится очистить «старые» записи? PARTITION by RANGE (TO_DAYS (log_time)), вероятно, лучший способ. См. http://mysql.rjweb.org/doc.php/partitionmaint. (И обратите внимание, что ПК должен быть (id, log_time).)
  • «Обрезка раздела» не может произойти, поскольку log_time похоронен в паре функций. Используйте перефразирование @ axiac.
  • innodb_buffer_pool_size должно быть установлено около 70% доступной ОЗУ.
+0

ЕСЛИ он работает всего лишь 1 запрос, этот индекс DROP будет иметь смысл. Вопрос: кто выполняет только запрос или 2? – Mihai

+0

@Mihai. Дело в том, что 'INDEX (домен, )' может обрабатывать все предложения WHERE, которые выиграют от 'INDEX (domain)'. Таким образом, практически нет причин иметь оба индекса. –

+0

@Rick Спасибо за ваш комментарий, я обновил свой первый запрос и вставил еще один запрос, всего 3 запроса, вы могли бы предложить лучшие комбинации составных индексов для всех запросов – Rams

0

Для запросов, я вижу очень мало обмена индексами. Вместо этого, я бы голосовать за этих индексов (предполагается, что вы добавляете id ... AUTO_INCREMENT)

PRIMARY KEY(id) 
INDEX(campaign_id, domain, log_time) 
INDEX(subscriber_id, domain) 
INDEX(domain, log_type, log_time) 
INDEX(log_time) 

Вы должны еще рассмотреть остальные предложения (ENUM, INT, и т.д.). Это уменьшит площадь диска как по данным, так и по индексам. Меньше -> больше cacheable -> меньше ввода-вывода -> быстрее.

INDEX(log_time) не обязательно будет использоваться в любой из запросов, но я держал его, только в случае, если оптимизатор решает целевой ORDER BY вместо WHERE. У меня недостаточно информации для прогнозирования; Кроме того, я подозреваю, что оптимизатор может выбрать один индекс один раз, другой индекс в другой раз.

3 "составные" индексы могут иметь первые два столбца в любом порядке. Я решил смешивать вещи так, чтобы первый столбец отличался друг от друга, тем самым потенциально помогая любому Query # 4.

Этот ответ более «искусен», чем «наука»; Я думаю, что это будет так же хорошо, как и получится.

Смежные вопросы