2014-09-30 3 views
1

У меня есть следующий запрос, который работает довольно медленно при запуске его на тысячи записей.MySQL DATE_ADD работает слишком медленно с динамическим интервалом

SELECT 
    name, 
    id 
FROM 
    meetings 
WHERE 
    meeting_date < '2014-09-20 11:00:00' AND (
    meeting_date >= '2014-09-20 09:00:00' OR 
    DATE_ADD(meeting_date, INTERVAL meeting_length SECOND) > '2014-09-20 09:00:00' 
) 

проверяет запрос, если meeting_date перекрывается в любом случае между 2014-09-20 09:00:00 и 2014-09-20 11:00:00. Вышеупомянутый запрос охватывает все возможные случаи перекрытия. Однако DATE_ADD добавляет много накладных расходов.

Во всяком случае для оптимизации DATE_ADD? Удаление DATE_ADD значительно повышает производительность, но не охватывает все перекрывающиеся случаи.

+0

Есть ли верхняя граница значения 'meeting_length'? –

+1

Не думайте, что это действительно функция DATE_ADD сама по себе; скорее, это число строк, для которых выражение должно быть оценено, из-за 'OR', и потому, что предикат в выражении не поддается сопоставлению. – spencer7593

+0

@ OllieJones, на самом деле не верхняя граница длины встречи, но безопасно принимать не более 24 часов. –

ответ

2

Я рекомендую вам устранить OR. MySQL не будет (не может) выполнить операцию сканирования диапазона по индексу на meeting_date, когда этот столбец обернут в функцию (когда сравнение не выполняется в столбце «голый», но сравнивается с результатом выражения, которое имеет для каждой строки.)

Для большого стола индекс с ведущим столбцом meeting_date, очевидно.

Я думаю, что «трюк», чтобы получить более высокую производительность, чтобы переписать запрос, чтобы ввести некоторые дополнительные знания предметной области. В частности, каковы значения MINIMUM и MAXIMUM для meeting_length?

Я думаю, что это довольно безопасно предположить, что это не будет отрицательным. И мы, вероятно, не ожидаем, что он будет равен нулю. Но даже если минимальная длина больше нуля, мы можем использовать нуль как наш «известный» минимум. (Это окажется более удобным, чем какое-либо другое ненулевое значение.)

Что нам действительно нужно знать, это МАКСИМАЛЬНОЕ значение для meeting_length. Если это известное постоянное значение, это было бы здорово, потому что мы собираемся включить это значение в запрос. допустим, максимальное значение meeting_length - это количество секунд в 7 дней.

В качестве демонстрации того, что я имею в виду:

SELECT m.name 
    , m.id 
    FROM meetings m 
WHERE m.meeting_date < '2014-09-20 11:00:00' 
    AND m.meeting_date > '2014-09-20 09:00:00' + INTERVAL -7 DAY 
HAVING m.meeting_date + INTERVAL meeting_length SECOND 
         > '2014-09-20 09:00:00' 

Давайте разворачивать, что немного.

Первый предикат такой же, как и в вашем исходном запросе ... «Начало» собрания - до «конец» указанного периода.

Третий предикат такой же, как и в вашем запросе ... «конец» собрания - после - начало указанного периода. (Мое личное предпочтение заключается в использовании формы + INTERVAL, чтобы добавить продолжительность в datetime.)

Итак, как и исходный запрос, мы ищем перекрытие.

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

Чтобы объяснить это немного ... если строка собрания, которая удовлетворяет условию «конец собрания после начала периода», тогда мы также знаем, для этой строки, что «начало собрания начинается (начало периода MINUS длина встречи) ». И мы также знаем, что «начало встречи после (период начинается MINUS MAXIMUM возможное значение длины встречи.

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

А это значит, что MySQL сможет использовать операцию сканирования диапазона индекса, чтобы удовлетворить это. Запрос имеет форму:

WHERE meeting_date > const 
    AND meeting_date < const 

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

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

Таким образом, нам по-прежнему нужна дополнительная проверка, чтобы далее фильтровать строки. Но это не нужно оценивать для каждой строки, только строки, которые проходят через первые два предиката.

AND meeting_date + length > const 

Нам просто нужно MySQL, чтобы признать, что length никогда не будет отрицательным; признать, что это на самом деле «более строгий» диапазон, а не более широкий диапазон. Он может работать с AND, но мы можем заставить MySQL оценить это условие позже, включив его в предложение HAVING.

HAVING meeting_date + length > const 

Но все это на самом деле просто догадка.

Нам действительно нужно взглянуть на выход EXPLAIN.

Если этот индекс с ведущим столбцом meeting_date также включает столбцы id и name, то MySQL может полностью удовлетворить запрос из индекса, без необходимости ссылаться на страницы в базовой таблице. (Если это произойдет, мы увидим «Использование индекса» в выводе EXPLAIN.)


Ранее я сказал, что это было бы удобно, если бы у нас была известна константа для максимальной meeting_length.

Мы могли бы также использовать запрос, чтобы определить, что из данных:

SELECT MAX(meeting_length) FROM meetings 

(А индекс с meeting_length в качестве ведущего столбца будет избежать необходимости делать дорогостоящую полную проверку таблицы)

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

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

Мы могли бы попробовать как подзапрос:

SELECT m.name 
    , m.id 
    FROM meetings m 
WHERE m.meeting_date < '2014-09-20 11:00:00' 
    AND m.meeting_date > '2014-09-20 09:00:00' 
         - INTERVAL (SELECT MAX(l.meeting_length) FROM meetings l) DAY 
HAVING m.meeting_date + INTERVAL meeting_length SECOND 
         > '2014-09-20 09:00:00' 

Или попробовать его как вложенное представление:

SELECT m.name 
    , m.id 
    FROM (SELECT MAX(l.meeting_length) AS max_seconds 
      FROM meetings l 
     ) d 
CROSS 
    JOIN meetings m 
WHERE m.meeting_date < '2014-09-20 11:00:00' 
    AND m.meeting_date > '2014-09-20 09:00:00' 
         - INTERVAL d.max_seconds SECOND 
HAVING m.meeting_date + INTERVAL meeting_length SECOND 
         > '2014-09-20 09:00:00' 
+0

Спасибо за подробный ответ. Действительно, это трюк. Я использовал подзапрос, чтобы захватить max meeting_length. –

+0

@PeteDarrow: Этот подзапрос должен оцениваться только один раз, а затем правая часть сравнения будет по существу постоянной. Выход EXPLAIN должен показывать операцию «диапазон» по индексу; Я не уверен, что это покажет «const», но по крайней мере он не должен показывать DEPENDENT SUBQUERY. Пока кто-то не добавляет строку с огромным значением meeting_length, это должно ограничивать количество проверяемых строк. – spencer7593

+0

@PeteDarrow: Вы экспериментировали с заменой ключевого слова 'HAVING' на' AND', чтобы проверить производительность, когда этот третий предикат перемещен в предложение WHERE? (Это может либо пойти ужасно, вернувшись к производительности так, как это было раньше. Или это может немного улучшить производительность.) Предикаты в предложении WHERE оцениваются по достижению строк. ограничивающие строки. Предикаты в предложении HAVING получают оценку намного позже, после того, как все строки были извлечены.) – spencer7593