Как оптимизировать длинный запрос MySQL с большим количеством объединений

#mysql #query-optimization

#mysql #оптимизация запросов

Вопрос:

У меня есть этот запрос, с которым мне нужна помощь. Итак, есть таблица insertjobticket , вызываемая с помощью столбца DEL , который представляет собой длинное символьное поле, в котором может быть несколько дат. Мне нужно создать выходную таблицу, которая содержит одну строку для каждого времени, когда в DEL поле есть дата для определенного диапазона дат.

Причина, по которой я не могу просто сделать более простой select ... where ... DEL like "%my_date%" , заключается в том, что DEL столбец может содержать несколько дат, и если это так, мне нужно вернуть несколько строк в выходной набор, по одной строке для каждой даты, которая появляется в DEL столбце.

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

 create temporary table jobtrack.ship_helpert3 as 
select * from 
(
    (
    select  
        date_format(now() - interval 3 day, '%m/%d/%Y') as `Ship_Date`,
        more_columns 
    from   
        jobticket.insertjobticket 
    where         
        DEL like concat('%',date_format(now() - interval 3 day, '%m/%d/%Y'),'%')
    ) union (
    select  
        date_format(now()   interval 2 day, '%m/%d/%Y') as `Ship_Date`,
        more_columns 
    from   
        jobticket.insertjobticket 
    where         
        DEL like concat('%',date_format(now()   interval 2 day, '%m/%d/%Y'),'%')
    ) union (
    select  
        date_format(now()   interval 1 day, '%m/%d/%Y') as `Ship_Date`, 
        more_columns 
    from   
        jobticket.insertjobticket 
    where         
        DEL like concat('%',date_format(now()   interval 1 day, '%m/%d/%Y'),'%')
    ) union ...
) t;
  

Каждый select запрос проверяет, есть ли какие-либо строки с определенной строкой даты ( date_format(now() interval @x day, '%m/%d/%Y') ) в DEL поле. Запрос создается программно и может быть очень длинным, так как я хотел бы иметь возможность проверять запрос для многих дат.

insertjobticket Таблица содержит 40 тыс. строк и растет, поэтому выполнение приведенного выше запроса занимает слишком много времени. Я понимаю, почему это занимает так много времени, потому что каждый union фактически должен создавать свой собственный подзапрос, который сканирует всю таблицу снова и снова для каждой даты. Я просто не знаю, как сделать эту работу более эффективной.

Кто-нибудь знает, как ускорить этот запрос?

Спасибо за помощь и дайте мне знать, если нам нужны дополнительные разъяснения.

Комментарии:

1. Честно говоря, есть только одно решение: создайте новую таблицу, содержащую одну дату в строке, вместо того, чтобы содержать все это в одном столбце DEL . Если вы не можете изменить свое приложение, сделайте это с помощью триггера и обновляйте новую таблицу всякий раз, когда вы обновляете свой столбец. Все остальное, что вы делаете, будет просто лоскутным (и в основном означает создание этой новой таблицы при каждом запуске этого запроса).

2. ДА. Нормализуйте свою схему. Или не утруждайте себя использованием СУБД.

3. Я полностью понимаю это предложение и благодарю за это. Часто нам приходится работать с запросом клиента, работающим в контексте существующей инфраструктуры. Определенно не идеально так, как есть.

4. @jeffery_the_wind Вы имеете в виду, что не можете использовать новую таблицу? Хорошо, тогда я напишу вам обходной путь.

5. @Solarflare ну, что я делаю в своем запросе, так это создаю новую таблицу, как вы можете видеть, запрос создает временную таблицу. Результаты, отображаемые пользователю, на самом деле являются запросом этой временной таблицы. Итак, я имею в виду, что я мог бы создать эту таблицу как постоянную и обновлять ее с помощью триггеров, но я старался держаться подальше от использования триггеров в целом. Итак, на данный момент запрос работает, но просто медленно.

Ответ №1:

Как уже говорилось в комментариях, единственным правильным решением было бы нормализовать ваши данные, то есть создать новую таблицу с одной датой доставки и первичным ключом insertjobticket для каждой строки, и позволить приложению использовать эту таблицу напрямую вместо столбца del или, по крайней мере, косвенно с помощью триггера, который обновляет этотаблица обновляется каждый раз, когда столбец DEL обновляется.

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

 select 
  del_dates.Ship_Date, 
  othercolumns 
from insertjobticket
join (
    select concat(date_format(now()   interval 2 day, '%m/%d/%Y')) 
           collate utf8_general_ci as Ship_Date 
    union select concat(date_format(now()   interval 1 day, '%m/%d/%Y')) 
    union select concat(date_format(now()   interval -15 day, '%m/%d/%Y')) 
    ...  
) del_dates
on insertjobticket.del like concat('%', del_dates.Ship_Date, '%');
  

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

Это в основном будет выполнять необходимый шаг нормализации (для запрошенных дат) каждый раз, когда вы выполняете запрос, и не сможет использовать индексы. Просто убедитесь, что ваш explain вывод отображается using join buffer для производной таблицы, а не для insertjobticket , в противном случае замените join на a straight_join .

Для 40 тыс. строк это может быть не большой проблемой, и в любом случае другого пути нет, кроме реальной нормализации. Имейте в виду, что ваш запрос будет линейно замедляться с количеством строк (400 тыс. строк будут занимать примерно в 10 раз больше времени, чем 40 тыс.), что предотвратит эффект индексов. Поэтому, если он слишком медленный сейчас (или иногда в будущем), вам в конечном итоге придется его нормализовать (или, в качестве обходного пути для проблем, создаваемых этим обходным путем, добавьте столбец, чтобы пометить старые записи и исключить их из условия соединения).

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

Комментарии:

1. Это отличный ответ. Спасибо за обходной запрос, я попробую посмотреть, ускорит ли это процесс. Я понимаю, что вы говорите о «правильном» способе сделать это, используя столбец DEL в своей собственной таблице. Использование поля даты вместо varchar. Но я не совсем понимаю, что вы имеете в виду в нижней части об индексах. Как мне использовать индексы для ускорения запроса?

2. @jeffery_the_wind Индексы являются важной частью баз данных, и их объяснение выходит далеко за рамки комментария. Вы действительно должны прочитать об этом. Вкратце: представьте, как вы будете искать свой номер телефона в телефонной книге по вашему нику («дата»). Вы не читаете его от начала до конца — поскольку он упорядочен, вы можете найти результат намного быстрее (и почти независимо от общего количества записей). Но если вы напишете два псевдонима, например, вашей жены и детей («2-я, 3-я, 4-я дата доставки») рядом друг с другом, будет трудно найти их номер телефона. Это в основном ваш дизайн базы данных.

3. Спасибо, это хорошее объяснение даже в комментарии.

Ответ №2:

При использовании базы данных вы должны учитывать возможное использование хранимых данных.

В этом случае вам нужно было выполнить синтаксический DEL анализ, когда вы собирались его сохранить, и создать другую таблицу из пар дат (похороненных в DEL) и идентификаторов (insertjobticket).

Попытка выполнить синтаксический анализ после факта намного медленнее и приводит к проблемам масштабирования.