Bireysel sorgular 10ms'de çalışır, UNION ALL ile 290ms + alır (7.7M kayıtları MySQL DB). Nasıl optimize edilir?


9

İki tür eklemeye izin veren öğretmenler için kullanılabilir randevuları depolayan bir masam var:

  1. Saatlik tabanlı : öğretmen başına günlük sınırsız alan ekleme özgürlüğüyle . Bir kişiye belirli bir öğretmen zamanı / alanı seçildikten sonra servis yapılır.

  2. Süre / aralık : 15 / Nisan'da başka bir öğretmen 10:00 ila 12:00 ve ardından 14:00 ila 18:00 saatleri arasında çalışabilir. Bir kişiye varış sırasına göre hizmet verilir, bu nedenle bir öğretmen 10:00 ile 12:00 arasında çalışırsa, bu dönemde gelen tüm kişilere varış sırası (yerel kuyruk) katılacaktır.

Mevcut tüm öğretmenleri bir aramada geri göndermem gerektiğinden, tüm alanların varış aralıklarının sırası ile aynı tabloya kaydedilmesi gerekir. Bu şekilde ASC'den date_from'a göre sipariş verebilirim ve arama sonuçlarında ilk kullanılabilir alanları gösterir.

Mevcut tablo yapısı

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;

Arama sorgusu

Filtre uygulamak gerekir: gerçek datetime, city_id, konu_kimliği ve bir yuva varsa (status = 0).

İçin Saatlik dayalı her öğretmen için ilk yakındaki uygun gün için mevcut tüm yuvaları göstermek zorunda (belirli bir günde tüm zaman aralıkları göstermek ve aynı öğretmen için bir günden fazla gösteremez). (Sorguyu mattedgod yardımı ile aldım ).

İçin bazlı aralık (order_of_arrival = 1), ben en yakındaki uygun aralık, öğretmen başına sadece bir kez göstermek zorundayız.

İlk sorgu tek başına yaklaşık 0.10 ms, ikinci sorgu 0.08 ms ve UNION ALL ortalama 300ms'de çalışır.

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

Soru

BİRLİĞİ optimize etmenin bir yolu var mı, bu yüzden maksimum bir ~ 20 ms makul bir yanıt alabilir veya hatta sadece bir sorguya dayalı (saatlik bir IF, vb.) + Dönüş aralığı tabanlı?

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

DÜZENLE:

Ben sadece tarih depolanmış bir alan "only_date_from" oluşturarak bazı denormalization denedim, bu yüzden bu değiştirebilir ...

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

... buna

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

Beni zaten 100ms kurtardı! Hala ortalama 200 ms.

Yanıtlar:


1

İlk olarak, orijinal sorgunuz "doğru" olmayabilir düşünüyorum; Senin SQLFiddle atıfla, bu satırlarla döndürmesi gereken sanki bana bakıyor ID= 2, 3ve 4(satırdaki ek olarak ID= 1sen edilmektedir sen sanki mevcut mantık göründüğünden, bu yarısından itibaren alma) amaçlanan bu satırlar için eklenmesi gerekir, çünkü açıkça OR (date_from >= '2014-04-10 08:00:00')ikinci WHEREfıkra bölümünüzle buluşurlar .

GROUP BY teacher_idAramalarınızdan sizin ikinci bölümünde fıkra UNIONo satırları kaybetmeye neden oluyor. Bunun nedeni, aslında seçim listenizdeki sütunları bir araya getirmemenizdir ve bu durumda GROUP BY'tanımlaması zor' davranışa neden olur.

Ayrıca, kötü performansınızı açıklayamasam da UNION, sorgunuzdan açıkça kaldırarak sizin için çalışabilirim:

Aynı tablodan satır almak için iki ayrı (ve kısımlar halinde, yinelenen) mantık kümesi kullanmak yerine, mantığınızı bir sorguda birleştirerek mantığınızdaki farklılıklar bir ORaraya getirdim. orijinal WHEREcümleleriniz dahil edilmiştir. Bu mümkündür çünkü (INNER) JOINbulmak için kullandığınız yerine closestDatebir LEFT JOIN.

Bu , bir satıra hangi mantık kümesinin uygulanması gerektiğini LEFT JOINartık ayırt edebildiğimiz anlamına gelir ; Birleştirme çalışırsa (en yakın tarih NULL DEĞİLDİR), mantığınızı ilk yarıdan uygularız, ancak birleştirme başarısız olursa (en yakınDate IS NULL), mantığı ikinci yarınızdan uygularız.

Bu, sorgunuzun döndürdüğü tüm satırları döndürür (kemanda) ve ayrıca bu ek olanları da alır.

  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;

Dahası, size "in fişi" gerek kalmamasıdır ayrıca böylece sorgu "çeki düzen" olabilir status, city_idve subject_idbir kereden fazla parametreleri.

Bunu yapmak için, bu sorguları ada seçmek ve bu sütunlar üzerinde gruplamak için alt sorguyu değiştirin . Ardından, JOINbireyin ONfıkra onların için bu sütunları eşleştirmek için gerekir ts.xxxeşdeğeri.

Bunun performansı olumsuz etkileyeceğini düşünmüyorum, ancak büyük bir veri kümesi üzerinde test yapmadan emin olamadım.

Yani katılımınız daha çok şöyle görünecek:

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

Bu sorguyu deneyin:

(
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;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.