Індивідуальні запити виконуються в 10 мс, при UNION ALL вони беруть 290 мс + (7,7 М записів MySQL БД). Як оптимізувати?


9

У мене є таблиця, в якій зберігаються доступні зустрічі для вчителів, що дозволяють два види вставок:

  1. Погодинна основа : з цілковитою свободою додавати необмежену кількість слотів на день на кожного вчителя (доки слоти не перетинаються): 15 квітня у вчителя можуть бути слоти на 10:00, 11:00, 12:00 та 16:00 . Людина обслуговується після вибору конкретного часу / слоту викладача.

  2. Період часу / діапазон : 15 квітня інший викладач може працювати з 10:00 до 12:00, а потім з 14:00 до 18:00. Людину обслуговують за наказом про прибуття, тому якщо вчитель працює з 10:00 до 12:00, усі особи, які приїхали в цей період, будуть відвідувати замовлення на прибуття (місцева черга).

Оскільки я повинен повернути всіх доступних викладачів у пошуку, мені потрібно зберегти всі слоти в тій самій таблиці, що і порядок прильоту. Таким чином я можу замовити дату з ASC, показуючи перші доступні слоти спочатку в результатах пошуку.

Поточна структура таблиці

CREATE TABLE `teacher_slots` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `teacher_id` mediumint(8) unsigned NOT NULL,
  `city_id` smallint(5) unsigned NOT NULL,
  `subject_id` smallint(5) unsigned NOT NULL,
  `date_from` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `date_to` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `status` tinyint(4) NOT NULL DEFAULT '0',
  `order_of_arrival` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `by_hour_idx` (`teacher_id`,`order_of_arrival`,`status`,`city_id`,`subject_id`,`date_from`),
  KEY `order_arrival_idx` (`order_of_arrival`,`status`,`city_id`,`subject_id`,`date_from`,`date_to`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Пошуковий запит

Мені потрібно фільтрувати за: фактичним датою, city_id, subject_id та якщо слот доступний (статус = 0).

Для погодинної основи я повинен показати всі доступні слоти для першого найближчого доступного дня для кожного вчителя (показати все тимчасові інтервали в певний день і не можу показати більше , ніж один день для того ж самого вчителя). (Я отримав запит за допомогою від mattedgod ).

Для діапазону (order_of_arrival = 1) я повинен показати найближчий доступний діапазон, лише один раз на кожного вчителя.

Перший запит виконується індивідуально приблизно за 0,10 мс, другий запит 0,08 мс, а СПІЛЬ ВСІ В середньому 300 мс.

(
    SELECT id, teacher_slots.teacher_id, date_from, date_to, order_of_arrival
    FROM teacher_slots
    JOIN (
        SELECT DATE(MIN(date_from)) as closestDay, teacher_id
        FROM teacher_slots
        WHERE   date_from >= '2014-04-10 08:00:00' AND order_of_arrival = 0
                AND status = 0 AND city_id = 6015 AND subject_id = 1
        GROUP BY teacher_id
    ) a ON a.teacher_id = teacher_slots.teacher_id
    AND DATE(teacher_slots.date_from) = closestDay
    WHERE teacher_slots.date_from >= '2014-04-10 08:00:00'
        AND teacher_slots.order_of_arrival = 0
        AND teacher_slots.status = 0
        AND teacher_slots.city_id = 6015
        AND teacher_slots.subject_id = 1
)

UNION ALL

(
    SELECT id, teacher_id, date_from, date_to, order_of_arrival
    FROM teacher_slots
    WHERE order_of_arrival = 1 AND status = 0 AND city_id = 6015 AND subject_id = 1
        AND (
            (date_from <= '2014-04-10 08:00:00' AND  date_to >= '2014-04-10 08:00:00')
            OR (date_from >= '2014-04-10 08:00:00')
        )
    GROUP BY teacher_id
)

ORDER BY date_from ASC;

Питання

Чи є спосіб оптимізувати Спілкування, щоб я міг отримати розумну відповідь максимум ~ 20 мс або навіть діапазон повернення на основі + погодинний, що базується лише на одному запиті (з ПЧ тощо)?

SQL Fiddle: http://www.sqlfiddle.com/#!2/59420/1/0

Редагувати:

Я спробував деяку денормалізацію, створивши поле "only_date_from", де я зберігав лише дату, так що я міг змінити це ...

DATE(MIN(date_from)) as closestDay / DATE(teacher_slots.date_from) = closestDay

... до цього

MIN(only_date_from) as closestDay / teacher_slots.only_date_from = closestDay

Це вже врятувало мене 100 мс! Ще в середньому 200 мс.

Відповіді:


1

По-перше, я думаю, що ваш оригінальний запит може бути не "правильним"; З посиланням на ваш SQLFiddle, він дивиться на мене , як ніби ви повинні повертатися рядками з ID= 2, 3і 4(в додатку до ряду з ID= 1ви будете отримувати від половини), тому що існуюча логіка виглядає як ніби ви призначені для цих інших рядків щоб вони були включені, оскільки вони явно відповідають OR (date_from >= '2014-04-10 08:00:00')частині вашого другого WHEREпункту.

GROUP BY teacher_idПункт у вашій другій частині вашого UNIONзаподіює вам втратити ці рядки. Це тому, що ви насправді не агрегуєте жодні стовпці у списку вибору, і в цьому випадку GROUP BYповедінка спричинить «важко визначити».

Крім того, хоча я не можу пояснити низьку ефективність вашої роботи UNION, я можу обходити це за вас, видаляючи його з вашого запиту:

Замість того, щоб використовувати два окремих (і частинами повторювані) набори логіки для отримання рядків з однієї таблиці, я об'єднав вашу логіку в один запит з відмінностями вашої логіки ORed разом - тобто якщо рядок відповідає одному чи іншому Ваших оригінальних WHEREпропозицій, вони включені. Це можливо тому , що я замінив би (INNER) JOINви використовували , щоб знайти closestDateз LEFT JOIN.

Це LEFT JOINозначає, що тепер ми також можемо розрізнити, який набір логіки слід застосовувати до ряду; Якщо з'єднання працює (ближчеDate НЕ НУЛЬНИЙ), ми застосовуємо вашу логіку з першого півріччя, але якщо приєднання не вдалося (najbliDate IS NULL), ми застосуємо логіку з вашої другої половини.

Таким чином, це поверне всі рядки, які повернув ваш запит (у скрипці), а також підбере ці додаткові.

  SELECT
    *

  FROM 
    teacher_slots ts

    LEFT JOIN 
    (
      SELECT 
        teacher_id,
        DATE(MIN(date_from)) as closestDay

      FROM 
        teacher_slots

      WHERE   
        date_from >= '2014-04-10 08:00:00' 
        AND order_of_arrival = 0
        AND status = 0 
        AND city_id = 6015 
        AND subject_id = 1

      GROUP BY 
        teacher_id

    ) a
    ON a.teacher_id = ts.teacher_id
    AND a.closestDay = DATE(ts.date_from)

  WHERE 
    /* conditions that were common to both halves of the union */
    ts.status = 0
    AND ts.city_id = 6015
    AND ts.subject_id = 1

    AND
    (
      (
        /* conditions that were from above the union 
           (ie when we joined to get closest future date) */
        a.teacher_id IS NOT NULL
        AND ts.date_from >= '2014-04-10 08:00:00'
        AND ts.order_of_arrival = 0
      ) 
      OR
      (
        /* conditions that were below the union 
          (ie when we didn't join) */
        a.teacher_id IS NULL       
        AND ts.order_of_arrival = 1 
        AND 
        (
          (
            date_from <= '2014-04-10 08:00:00' 
            AND  
            date_to >= '2014-04-10 08:00:00'
          )

          /* rows that met this condition were being discarded 
             as a result of 'difficult to define' GROUP BY behaviour. */
          OR date_from >= '2014-04-10 08:00:00' 
        )
      )
    )

  ORDER BY 
   ts.date_from ASC;

Крім того, ви можете «привести в порядок» запит далі , так що вам не потрібно «вилки в» ваших status, city_idі subject_idпараметри більш ніж один раз.

Для цього змініть підзапит, aщоб також вибрати ці стовпці, а також згрупувати їх. Тоді, з пунктом JOIN's ONпотрібно буде зіставити ці стовпці з їх ts.xxxеквівалентами.

Я не думаю, що це негативно вплине на продуктивність, але не можу бути впевненим без тестування на великому наборі даних.

Тож ваше приєднання буде виглядати більше:

LEFT JOIN 
(
  SELECT 
    teacher_id,
    status,
    city_id,
    subject_id,
    DATE(MIN(date_from)) as closestDay

  FROM 
    teacher_slots

  WHERE   
    date_from >= '2014-04-10 08:00:00' 
    AND order_of_arrival = 0
  /* These no longer required here...
    AND status = 0 
    AND city_id = 6015 
    AND subject_id = 1
  */

  GROUP BY 
    teacher_id,
    status,
    city_id,
    subject_id

) a
ON a.teacher_id = ts.teacher_id
AND a.status = ts.status 
AND a.city_id = ts.city_id 
AND a.subject_id = ts.city_id
AND a.closestDay = DATE(ts.date_from)

2

Спробуйте цей запит:

(
select * from (SELECT id, teacher_slots.teacher_id, date_from, date_to,  order_of_arrival
FROM teacher_slots  WHERE teacher_slots.date_from >= '2014-04-10 08:00:00'
    AND teacher_slots.order_of_arrival = 0
    AND teacher_slots.status = 0
    AND teacher_slots.city_id = 6015
    AND teacher_slots.subject_id = 1) 
 teacher_slots
JOIN (
    SELECT DATE(MIN(date_from)) as closestDay, teacher_id
    FROM teacher_slots
    WHERE   date_from >= '2014-04-10 08:00:00' AND order_of_arrival = 0
            AND status = 0 AND city_id = 6015 AND subject_id = 1
    GROUP BY teacher_id
) a ON a.teacher_id = teacher_slots.teacher_id
AND DATE(teacher_slots.date_from) = closestDay

)

UNION ALL

(
SELECT id, teacher_id, date_from, date_to, order_of_arrival
FROM teacher_slots
WHERE order_of_arrival = 1 AND status = 0 AND city_id = 6015 AND subject_id = 1
    AND (
        (date_from <= '2014-04-10 08:00:00' AND  date_to >= '2014-04-10 08:00:00')
        OR (date_from >= '2014-04-10 08:00:00')
    )
GROUP BY teacher_id
)

ORDER BY date_from ASC;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.