Низкая производительность запросов с индексом по сравнению с без индекса - PullRequest
0 голосов
/ 15 мая 2018

Я использую MySQL 5.6 и имею таблицу, разбитую на столбец «network_date» типа DATE (каждый день имеет раздел, например, «2018-05-01», и каждый раздел содержит приблизительно 400 000 строк).Таблица имеет два составных индекса (не уникальных), которые также включают столбец network_date (сначала в порядке 6 столбцов).Это следующие индексы:

  1. _daily_ad_level_demand_idx: network_date, publisher_network_id, display_advertiser_id, business_rule_id, campaign_id, ad_id
  2. _daily_ad_level_supply_idid *, 100 *, издатель сети: publisher_idis 100: publisher_idisid: 100%

Однако, согласно команде EXPLAIN, при выполнении следующего запроса:

EXPLAIN EXTENDED SELECT 
    network_date,
    SUM(COALESCE(ad_view, 0)) AS ad_view,
    SUM(COALESCE(ad_spend_network, 0)) AS ad_spend_network,
    SUM(COALESCE(ad_click, 0)) AS ad_click,
    campaign_id,
    display_advertiser_id,
    publisher_network_id,
    ad_id
FROM
    daily_ad_level
WHERE
    (publisher_network_id = 16020)
    AND network_date BETWEEN STR_TO_DATE('2018-04-15 00:00:00.000000',
        '%Y-%m-%d %H:%i:%S.%f') AND STR_TO_DATE('2018-05-12 23:59:59.999000',
        '%Y-%m-%d %H:%i:%S.%f')
GROUP BY campaign_id, network_date, display_advertiser_id, 
publisher_network_id, ad_id

индекс не выбирается оптимизатором и выполняется полное сканирование таблицы.Вы можете увидеть результат здесь: Вывод команды EXPLAIN с указанием 'network_date' в индексе

После некоторых исследований и размышлений над этим вопросом я решил удалить столбец 'network_date' из индексов- удаление разделов должно в любом случае выполнять необходимый поиск, так что, по-видимому, избыточно включать его в индекс.Повторное выполнение команды EXPLAIN показывает, что теперь выбирается индекс.Вы можете увидеть результат здесь: Вывод команды EXPLAIN с no 'network_date', включенным в индекс

С точки зрения длительности запроса производительность снизилась на , когдаоптимизатор выбрал индекс : от 9,75 с до 12,4 с ... Вопрос в том, почему ???

Анализ выходных данных команды объяснения first (безиспользование индекса), можно видеть, что столбцы «отфильтрованные» и «строки» имеют значения 50,00 и 4 474 281 соответственно.Может ли быть так, что оптимизатор делает вывод, что полное сканирование таблицы дешевле, чем использование индекса, который исключит только около половины строк?Если это так, я бы ожидал того же поведения во втором сценарии, а это не так: оптимизатор выбирает индекс, а запрос работает плохо.

Кто-нибудь знает, что может вызвать такое поведение?

Ответы [ 4 ]

0 голосов
/ 21 мая 2018

Шаг 1 - лучший индекс

Не начинайте индексы с network_date, заканчивайте их этим. Зачем? Как правило, после выполнения теста «диапазон» вы не можете использовать больше столбцов индекса.

Ваш первый запрос требует только

INDEX(publisher_network_id, network_date)  -- in this order

При оптимизации таблиц, которые больше, чем можно кэшировать в ОЗУ (buffer_pool), преобладающим фактором является попадание на диск. Этот индекс минимизирует количество обращений к диску.

Не связано: я не вижу необходимости заключать даты в STR_TO_DATE.

Шаг 2 - бросить разбиение , если не требуется

Вы по какой-то причине используете PARTITIONs?

  • Производительность - вряд ли поможет; конечно, не лучше, чем INDEX Я только что порекомендовал.
  • Очистка старых записей - очень веская причина.

Я не могу проанализировать оставшуюся часть вашего запроса, так как нет понятия, в какой таблице находится каждый столбец. Если, например, столбцы GROUP BY не все в одной таблице, есть нет способ использовать индекс для этого.

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

Есть ли другие вопросы, которые мы должны рассмотреть?

Шаг 3 - лучше кластеризованный ПЕРВИЧНЫЙ КЛЮЧ

  • избавиться от разбиения (если вам не нужно его очищать) и
  • Сделайте PRIMARY KEY началом с (publisher_network_id, network_date). (Выберите id или все, что необходимо для того, чтобы сделать его уникальным, поскольку PK должен быть уникальным.)

Почему это было бы еще лучше? Тогда все необходимые строки подряд ("кластеризованные") вместе, что минимизирует количество обращений к диску.

Конечно, тогда будет * временная таблица, сортировка и т. Д. Для GROUP BY, но это действительно может произойти в ОЗУ.

Шаг 4 - Сводная таблица

Хранилище данных включает в себя «отчеты». Их очень дорого извлечь из необработанных данных из-за того, сколько строк нужно прочитать. Создайте и поддерживайте сводную таблицу, в которой есть ряд (ы) для каждой комбинации клавиш, скажем, для каждого дня. Затем запустите «отчет» для этой таблицы; он может работать в 10 раз так быстро.

Подробнее о сводных таблицах: http://mysql.rjweb.org/doc.php/summarytables

0 голосов
/ 15 мая 2018

Я бы предложил добавить два индекса и переписать запрос.

ALTER TABLE daily_ad_level
ADD INDEX daily_ad_level_idx_id_date (publisher_network_id, network_date);

И

ALTER TABLE daily_ad_level
ADD INDEX daily_ad_level_idx_campaign_id_network_date_display_advertiser_id_publisher_network_id_ad_id (campaign_id, network_date, display_advertiser_id, 
publisher_network_id, ad_id);

Переписать запрос

Я предполагаю, чтостолбец ad_id - это ПЕРВИЧНЫЙ КЛЮЧ в вашей таблице

SELECT
    network_date,
    SUM(COALESCE(ad_view, 0)) AS ad_view,
    SUM(COALESCE(ad_spend_network, 0)) AS ad_spend_network,
    SUM(COALESCE(ad_click, 0)) AS ad_click,
    campaign_id,
    display_advertiser_id,
    publisher_network_id,
    ad_id
FROM (

    SELECT
     ad_id
    FROM  
     daily_ad_level
    WHERE
          publisher_network_id = 16020
        AND
          network_date BETWEEN STR_TO_DATE('2018-04-15 00:00:00.000000',
            '%Y-%m-%d %H:%i:%S.%f') AND STR_TO_DATE('2018-05-12 23:59:59.999000',
            '%Y-%m-%d %H:%i:%S.%f') 
    ) AS daily_ad_level_filterd

    INNER JOIN 
     daily_ad_level
    ON
     daily_ad_level_filterd.ad_id = daily_ad_level.ad_id 

    GROUP BY 
      campaign_id, network_date, display_advertiser_id, 
    publisher_network_id, ad_id
0 голосов
/ 16 мая 2018

После прочтения ваших комментариев, ребята, мне пришло в голову, что группа по порядку столбцов значительно влияет на производительность запроса, то есть, если я перегруппирую группу по столбцам в соответствии с порядком столбцов индекса(и добавление дополнительного столбца, который в данный момент отсутствует в запросе - business_rule_id) - результат извлекается за 0,23 секунды по сравнению с 9,23 секунды ранее!Более того, на этот раз оптимизатор выбирает правильный индекс.Вот модифицированный запрос:

SELECT 
    network_date,
    SUM(COALESCE(ad_view, 0)) AS ad_view,
    SUM(COALESCE(ad_spend_network, 0)) AS ad_spend_network,
    SUM(COALESCE(ad_click, 0)) AS ad_click,
    campaign_id,
    display_advertiser_id,
    publisher_network_id,
    ad_id
FROM
    daily_ad_level
WHERE
    (publisher_network_id = 16020)
    AND network_date BETWEEN STR_TO_DATE('2018-04-15 00:00:00.000000',
        '%Y-%m-%d %H:%i:%S.%f') AND STR_TO_DATE('2018-05-12 23:59:59.999000',
        '%Y-%m-%d %H:%i:%S.%f')
    GROUP BY  network_date, publisher_network_id ,display_advertiser_id, 
    business_rule_id, campaign_id, ad_id ;

Вы можете увидеть скриншот с результатом здесь: Оптимизированный вывод запроса

А вот скриншот с неоптимизированным результатом: Неоптимизированный вывод запроса

Хотя результаты не совсем совпадают (из-за добавления столбца business_rule_id к предложению group by), он все же дает хорошее представление о «образе мышления» оптимизатора, поэтому при правильномкорректировки, требуемый результат может быть достигнут.

Отличное руководство, ребята, спасибо!

0 голосов
/ 15 мая 2018

Вы должны начать с индексации полей по сравнению с оператором равенства (=).Затем вы должны добавить столбцы с операторами диапазона (>, <, МЕЖДУ, ...).В этом случае я не вижу причины индексировать столбцы в группе, так как не думаю, что оптимизатор выберет их.См. Рекомендуемый индекс ниже. </p>

Попробуйте добавить этот индекс:

ALTER TABLE `daily_ad_level` ADD INDEX `daily_ad_level_idx_id_date` (`publisher_network_id`,`network_date`);
...