Einzelne Abfragen werden auf 10 ms ausgeführt, wobei UNION ALL 290 ms + benötigt (7,7 Millionen Datensätze MySQL DB). Wie optimiere ich?

9

Ich habe eine Tabelle, in der verfügbare Termine für Lehrer gespeichert sind und zwei Arten von Einfügungen möglich sind:

  1. Stundenbasiert : mit der völligen Freiheit, unbegrenzte Zeitnischen pro Tag und Lehrer hinzuzufügen (solange sich die Zeitnischen nicht überschneiden): Am 15. April kann ein Lehrer Zeitnischen um 10:00, 11:00, 12:00 und 16:00 Uhr haben . Eine Person wird bedient, nachdem eine bestimmte Lehrerzeit / ein bestimmtes Zeitfenster ausgewählt wurde.

  2. Zeitraum / Bereich : Am 15. April kann ein anderer Lehrer von 10:00 bis 12:00 Uhr und dann von 14:00 bis 18:00 Uhr arbeiten. Eine Person wird in der Reihenfolge ihrer Ankunft bedient. Wenn also ein Lehrer von 10:00 bis 12:00 Uhr arbeitet, werden alle Personen, die in diesem Zeitraum ankommen, in der Reihenfolge ihrer Ankunft (lokale Warteschlange) betreut.

Da ich bei einer Suche alle verfügbaren Lehrer zurückgeben muss, müssen alle Slots in derselben Tabelle wie die Reihenfolge der Ankunftsbereiche gespeichert werden. Auf diese Weise kann ich nach Datum_von ASC bestellen und die ersten verfügbaren Slots zuerst in den Suchergebnissen anzeigen.

Aktuelle Tabellenstruktur

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;

Suchanfrage

Ich muss filtern nach: Istdatum / Uhrzeit, Stadt-ID, Betreff-ID und ob ein Steckplatz verfügbar ist (Status = 0).

Für stündlich muss ich für jeden Lehrer alle verfügbaren Slots für den nächstgelegenen verfügbaren Tag anzeigen (alle Zeitfenster eines bestimmten Tages anzeigen und kann nicht mehr als einen Tag für denselben Lehrer anzeigen). (Ich habe die Anfrage mit Hilfe von mattedgod bekommen ).

Für Bereich basierte (order_of_arrival = 1), ich habe den am nächsten verfügbaren Bereich zeigen, nur einmal pro Lehrer.

Die erste Abfrage wird einzeln in ca. 0,10 ms ausgeführt, die zweite Abfrage 0,08 ms und die UNION ALL durchschnittlich 300 ms.

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

Frage

Gibt es eine Möglichkeit, die UNION zu optimieren, damit ich in nur einer Abfrage (mit einer IF usw.) eine vernünftige Antwort von maximal ~ 20 ms oder sogar einen Rückgabebereich + stündlich erhalten kann?

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

BEARBEITEN:

Ich habe eine Denormalisierung versucht, indem ich ein Feld "only_date_from" erstellt habe, in dem ich nur das Datum gespeichert habe, damit ich dies ändern kann ...

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

... dazu

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

Es hat mich schon 100ms gerettet! Im Durchschnitt immer noch 200 ms.

AlfredBaudisch
quelle

Antworten:

1

Erstens denke ich, dass Ihre ursprüngliche Abfrage möglicherweise nicht "korrekt" ist. Mit Bezug auf Ihre SQLFiddle, sieht es für mich , als ob Sie Zeilen mit der Rückkehr sollte ID= 2, 3und 4(zusätzlich zu der Zeile mit ID= 1Sie sind von dieser Hälfte bekommen), weil Ihre bestehende Logik erscheint , obwohl Sie soll für diese anderen Reihen aufgenommen werden, da sie ausdrücklich den OR (date_from >= '2014-04-10 08:00:00')Teil Ihrer zweiten WHEREKlausel erfüllen .

Die GROUP BY teacher_idKlausel in Ihrem zweiten Teil von UNIONführt dazu, dass Sie diese Zeilen verlieren. Dies liegt daran, dass Sie tatsächlich keine Spalten in Ihrer Auswahlliste aggregieren. In diesem Fall GROUP BYführt dies zu einem schwer zu definierenden Verhalten.

Auch wenn ich die schlechte Leistung von Ihnen nicht erklären kann UNION, kann ich sie für Sie umgehen, indem ich sie direkt aus Ihrer Abfrage entferne:

Anstatt zwei separate (und teilweise sich wiederholende) Logiksätze zu verwenden, um Zeilen aus derselben Tabelle abzurufen, habe ich Ihre Logik in einer Abfrage zusammengefasst, wobei die Unterschiede in Ihrer Logik ORzusammengefasst sind - dh wenn eine Zeile auf die eine oder die andere trifft von Ihren ursprünglichen WHEREKlauseln ist es enthalten. Dies ist möglich, weil ich das (INNER) JOIN, mit dem Sie das gefunden haben, closestDatedurch ein ersetzt habe LEFT JOIN.

Dies LEFT JOINbedeutet, dass wir jetzt auch unterscheiden können, welcher Logiksatz auf eine Zeile angewendet werden soll. Wenn der Join funktioniert (nextDate IS NOT NULL), wenden wir Ihre Logik aus der ersten Hälfte an. Wenn der Join jedoch fehlschlägt (nextDate IS NULL), wenden wir die Logik aus Ihrer zweiten Hälfte an.

Dies gibt also alle Zeilen zurück, die Ihre Abfrage zurückgegeben hat (in der Geige), und nimmt auch diese zusätzlichen auf.

  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;

Darüber hinaus können Sie „aufzuräumen“ Ihre Anfrage weiter , so dass Sie nicht Ihr auf „in Plug“ brauchen status, city_idund subject_idmehr Parameter als einmal.

Ändern Sie dazu die Unterabfrage, aum auch diese Spalten auszuwählen und diese Spalten zu gruppieren. Dann müsste die Klausel JOIN's ONdiese Spalten ihren ts.xxxEntsprechungen zuordnen.

Ich denke nicht, dass dies die Leistung negativ beeinflusst, könnte aber nicht sicher sein, ohne einen großen Datensatz zu testen.

Ihr Join sieht also eher so aus:

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)
Sepster
quelle
2

Versuchen Sie diese Abfrage:

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