개별 쿼리는 10ms에서 실행되며 UNION ALL은 290ms 이상 (7.7M 레코드 MySQL DB)을 사용합니다. 최적화하는 방법?


9

교사를위한 가능한 약속을 저장하는 테이블이 있는데 두 가지 종류의 삽입이 가능합니다.

  1. 시간별 : 교사 당 하루에 무제한 슬롯을 추가 할 수있는 자유 (슬롯이 겹치지 않는 한) : 15/4 월 교사는 10:00, 11:00, 12:00 및 16:00에 슬롯을 가질 수 있습니다. . 특정 교사 시간 / 슬롯을 선택한 후 사람에게 서비스가 제공됩니다.

  2. 기간 / 범위 : 15/4 월 4 월 다른 교사는 10 : 00 ~ 12 : 00에 14 : 00 ~ 18 : 00에 일할 수 있습니다. 도착 순서대로 한 사람이 서비스되므로 교사가 10시에서 12 시까 지 근무하는 경우이 기간에 도착한 모든 사람은 도착 순서 (현지 대기열)로 참석합니다.

검색 할 때 사용 가능한 모든 교사를 반환해야하므로 도착 범위 순서와 동일한 테이블에 모든 슬롯을 저장해야합니다. 이렇게하면 date_from 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.10ms, 두 번째 쿼리는 0.08ms, UNION ALL은 평균 300ms에서 개별적으로 실행됩니다.

(
    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;

질문

UNION을 최적화하는 방법이 있습니까? 그래서 최대 ~ 20ms의 합리적인 응답을 얻거나 심지어 하나의 쿼리 (IF 등으로)를 기준으로 + 시간당 반환 범위를 얻을 수 있습니까?

SQL 피들 : 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

이미 100ms를 절약했습니다! 여전히 평균 200ms입니다.

답변:


1

첫째, 귀하의 원래 검색어가 "정확하지 않은"것으로 생각합니다. 당신의 SQLFiddle을 참조하면, 그것은 당신이 행을 반환해야하는 것처럼 나에게 보인다 ID= 2, 34(와 행에 추가 ID= 1당신이 하는 당신이 비록 기존의 논리가 나타나기 때문에,이 반에서 점점) 구성 이 다른 행에 대한 OR (date_from >= '2014-04-10 08:00:00')두 번째 WHERE조항 의 일부를 명시 적으로 충족하므로 포함되어야 합니다 .

GROUP BY teacher_id당신의 두 번째 부분에 절은 UNION당신이 그 행을 잃는 원인이된다. 실제로 선택 목록에서 열을 집계하지 않기 때문에이 경우 GROUP BY'정의하기 어려운'동작이 발생합니다.

또한의 성능 저하를 설명 할 수 없지만 UNION쿼리에서 완전히 제거하여 해결할 수 있습니다.

동일한 테이블에서 행을 가져 오기 위해 두 개의 별도 (및 부분적으로 반복되는) 논리 집합을 사용하는 대신 논리의 차이점을 함께 사용하여 논리를 하나의 쿼리로 통합했습니다. OR즉, 행 이 서로 일치하는 경우 원래 WHERE조항 중 포함됩니다. 이것은를 (INNER) JOIN찾기 위해 사용 했던를 (으)로 교체했기 때문에 가능 closestDate합니다 LEFT JOIN.

이것은 LEFT JOIN이제 어떤 로직 세트를 행에 적용해야하는지 구별 할 수 있음을 의미 합니다. 조인이 작동하면 (closestDate IS NOT NULL) 상반기부터 논리를 적용하지만 조인이 실패하면 (closestDate 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를 변경하여 해당 열을 선택하고 해당 열을 그룹화하십시오. 그런 다음 JOINON절에서 해당 열을 해당 열에 매핑해야합니다 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;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.