Các truy vấn riêng lẻ chạy trong 10ms, với UNION ALL, họ đang dùng 290ms + (7,7M bản ghi MySQL DB). Làm thế nào để tối ưu hóa?


9

Tôi có một bảng lưu trữ các cuộc hẹn có sẵn cho giáo viên, cho phép hai loại chèn:

  1. Dựa trên hàng giờ : với toàn quyền tự do thêm các vị trí không giới hạn mỗi ngày cho mỗi giáo viên (miễn là các vị trí không trùng nhau): vào ngày 15 tháng Tư, một giáo viên có thể có các vị trí vào lúc 10:00, 11:00, 12:00 và 16:00 . Một người được phục vụ sau khi chọn thời gian / thời gian dành cho giáo viên cụ thể.

  2. Khoảng thời gian / phạm vi : vào ngày 15 tháng Tư, một giáo viên khác có thể làm việc từ 10:00 đến 12:00 và sau đó từ 14:00 đến 18:00. Một người được phục vụ theo thứ tự đến, vì vậy nếu một giáo viên làm việc từ 10:00 đến 12:00, tất cả những người đến trong giai đoạn này sẽ được tham dự theo thứ tự đến (xếp hàng địa phương).

Vì tôi phải trả lại tất cả các giáo viên có sẵn trong một tìm kiếm, tôi cần tất cả các vị trí được lưu trong cùng một bảng với thứ tự các phạm vi đến. Bằng cách này, tôi có thể đặt hàng theo date_from ASC, hiển thị các vị trí có sẵn đầu tiên trước tiên trên kết quả tìm kiếm.

Cấu trúc bảng hiện tại

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;

Truy vấn tìm kiếm

Tôi cần lọc theo: datetime thực tế, city_id, topic_id và nếu có sẵn một vị trí (status = 0).

Đối với hàng giờ, tôi phải hiển thị tất cả các vị trí có sẵn cho ngày có sẵn gần nhất cho mỗi giáo viên (hiển thị tất cả các vị trí thời gian của một ngày nhất định và không thể hiển thị nhiều hơn một ngày cho cùng một giáo viên). (Tôi đã nhận được truy vấn với sự giúp đỡ từ mattedgod ).

Đối với phạm vi dựa trên (order_of_ariances = 1), tôi phải hiển thị phạm vi khả dụng gần nhất, chỉ một lần cho mỗi giáo viên.

Truy vấn đầu tiên chạy riêng lẻ trong khoảng 0,10 ms, truy vấn thứ hai 0,08 ms và UNION ALL trung bình 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;

Câu hỏi

Có cách nào để tối ưu hóa UNION, vì vậy tôi có thể nhận được phản hồi hợp lý ở mức tối đa ~ 20ms hoặc thậm chí trả về phạm vi dựa trên + hàng giờ chỉ trong một truy vấn (với IF, v.v.) không?

Câu đố về SQL: http://www.sqlfiddle.com/#!2/59420/1/0

BIÊN TẬP:

Tôi đã thử một số tính không chuẩn hóa bằng cách tạo trường "only_date_from" nơi tôi chỉ lưu trữ ngày, vì vậy tôi có thể thay đổi ...

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

... đến đây

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

Nó đã giúp tôi tiết kiệm 100ms! Vẫn trung bình 200ms.

Câu trả lời:


1

Thứ nhất, tôi nghĩ rằng truy vấn ban đầu của bạn có thể không "chính xác"; Với tham chiếu đến SQLFiddle của bạn, có vẻ với tôi như thể bạn nên quay trở lại hàng với ID= 2, 34(ngoài hàng với ID= 1bạn đang nhận được từ nửa này), vì lý hiện tại của bạn xuất hiện như thể bạn dành cho những hàng khác được bao gồm, vì họ đáp ứng rõ ràng OR (date_from >= '2014-04-10 08:00:00')một phần của WHEREmệnh đề thứ hai của bạn .

Các GROUP BY teacher_idđiều khoản trong phần thứ hai của bạn của bạn UNIONđang làm bạn mất những hàng. Điều này là do bạn không thực sự tổng hợp bất kỳ cột nào trong danh sách lựa chọn của mình và trong trường hợp này GROUP BYsẽ gây ra hành vi 'khó xác định'.

Ngoài ra, trong khi tôi không thể giải thích hiệu suất kém của bạn UNION, tôi có thể giải quyết vấn đề đó cho bạn bằng cách xóa hoàn toàn khỏi truy vấn của bạn:

Thay vì sử dụng hai bộ logic riêng biệt (và trong các phần, lặp lại) để lấy các hàng từ cùng một bảng, tôi đã hợp nhất logic của bạn thành một truy vấn với sự khác biệt trong logic của bạn ORvới nhau - nghĩa là nếu một hàng gặp nhau hoặc khác các WHEREđiều khoản ban đầu của bạn , nó bao gồm. Điều này là có thể bởi vì tôi đã thay thế cái (INNER) JOINbạn đang sử dụng để tìm cái closestDatebằng a LEFT JOIN.

Đây LEFT JOINcó nghĩa là chúng ta bây giờ cũng có thể phân biệt bộ của logic nên được áp dụng cho một hàng; Nếu phép nối hoạt động (nearDate IS NOT NULL), chúng tôi sẽ áp dụng logic của bạn từ nửa đầu, nhưng nếu phép nối không thành công (RecentDate IS NULL) thì chúng tôi sẽ áp dụng logic từ nửa sau của bạn.

Vì vậy, điều này sẽ trả về tất cả các hàng mà truy vấn của bạn trả về (trong fiddle) và nó cũng chọn những hàng bổ sung.

  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;

Hơn nữa, bạn có thể "dọn dẹp" truy vấn của bạn hơn nữa để bạn không cần phải "cắm" của bạn status, city_idsubject_idcác thông số nhiều hơn một lần.

Để làm điều này, thay đổi truy vấn con ađể chọn các cột đó và cũng nhóm trên các cột đó. Sau đó, mệnh đề JOIN' ONsẽ cần ánh xạ các cột đó tới các cột ts.xxxtương đương.

Tôi không nghĩ rằng điều này sẽ ảnh hưởng tiêu cực đến hiệu suất, nhưng không thể chắc chắn nếu không thử nghiệm trên một tập dữ liệu lớn.

Vì vậy, tham gia của bạn sẽ trông giống như:

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

Hãy thử truy vấn này:

(
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;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.