การสืบค้นแต่ละรายการรันใน 10ms โดย UNION ALL จะใช้เวลา 290ms + (7.7M บันทึก MySQL DB) วิธีเพิ่มประสิทธิภาพ


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 น. ทุกคนที่มาถึงในช่วงเวลานี้จะเข้าร่วมตามลำดับของการมาถึง (คิวท้องถิ่น)

เนื่องจากฉันต้องส่งคืนอาจารย์ที่มีอยู่ทั้งหมดในการค้นหาฉันจึงต้องการบันทึกช่องทั้งหมดในตารางเดียวกันตามลำดับของช่วงการมาถึง วิธีนี้ฉันสามารถสั่งซื้อโดย 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.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;

คำถาม

มีวิธีเพิ่มประสิทธิภาพยูเนี่ยนดังนั้นฉันสามารถได้รับการตอบสนองที่เหมาะสมของสูงสุด ~ 20ms หรือแม้แต่กลับช่วงตาม + รายชั่วโมงตามเพียงหนึ่งแบบสอบถาม (ด้วย IF ฯลฯ )?

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

มันช่วยฉันได้ 100ms แล้ว! โดยเฉลี่ย 200ms

คำตอบ:


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แต่ฉันสามารถแก้ไขให้คุณได้โดยการลบออกจากการค้นหาของคุณทันที:

แทนที่จะใช้ชุดของตรรกะสองชุด (และในส่วนที่ซ้ำกัน) เพื่อรับแถวจากตารางเดียวกันฉันได้รวมตรรกะของคุณเป็นหนึ่งแบบสอบถามด้วยความแตกต่างในตรรกะของคุณORด้วยกัน - เช่นถ้าแถวตรงกับหนึ่งหรืออื่น ๆ จากWHEREคำสั่งดั้งเดิมของคุณมันรวมอยู่ด้วย นี้เป็นไปได้เพราะผมเคยแทนที่(INNER) JOINคุณกำลังใช้เพื่อหาสิ่งที่มีclosestDateLEFT JOIN

นี้LEFT JOINหมายความว่าเราอยู่ในขณะนี้ยังสามารถที่จะแยกแยะความแตกต่างซึ่งชุดของตรรกะควรจะนำไปใช้กับแถว; หากการเข้าร่วมใช้งานได้ใกล้เคียงที่สุด (ไม่ใช่วันที่ NULL) เราจะใช้ตรรกะของคุณจากครึ่งแรก แต่หากการเข้าร่วมล้มเหลว (ที่อยู่ใกล้ที่สุดคือ 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ประโยคจะต้อง map คอลัมน์เหล่านั้นของพวกเขา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.