sql >> Database >  >> RDS >> Mysql

Hoe kan ik een afgeleide tabelquery die beter presteert dan het JOINed-equivalent verder optimaliseren?

Nou, ik heb een oplossing gevonden. Het kostte veel experimenteren, en ik denk een beetje blind geluk, maar hier is het:

CREATE TABLE magic ENGINE=MEMORY
SELECT
  s.shop_id AS shop_id,
  s.id AS shift_id,
  st.dow AS dow,
  st.start AS start,
  st.end AS end,
  su.user_id AS manager_id
FROM shifts s
JOIN shift_times st ON s.id = st.shift_id
JOIN shifts_users su ON s.id = su.shift_id
JOIN shift_positions sp ON su.shift_position_id = sp.id AND sp.level = 1

ALTER TABLE magic ADD INDEX (shop_id, dow);

CREATE TABLE tickets_extra ENGINE=MyISAM
SELECT 
  t.id AS ticket_id,
  (
    SELECT m.manager_id
    FROM magic m
    WHERE DAYOFWEEK(t.created) = m.dow
    AND TIME(t.created) BETWEEN m.start AND m.end
    AND m.shop_id = t.shop_id
  ) AS manager_created,
  (
    SELECT m.manager_id
    FROM magic m
    WHERE DAYOFWEEK(t.resolved) = m.dow
    AND TIME(t.resolved) BETWEEN m.start AND m.end
    AND m.shop_id = t.shop_id
  ) AS manager_resolved
FROM tickets t;
DROP TABLE magic;

Lange uitleg

Nu zal ik uitleggen waarom dit werkt, en mijn relatieve proces en stappen om hier te komen.

Ten eerste wist ik dat de query die ik probeerde te lijden had vanwege de enorme afgeleide tabel en de daaropvolgende JOIN's hierop. Ik nam mijn goed geïndexeerde ticketstabel en voegde alle shift_times-gegevens erop toe, en liet MySQL daarop kauwen terwijl het probeerde zich bij de shifts en shift_positions-tabel aan te sluiten. Deze afgeleide kolos zou tot een niet-geïndexeerde puinhoop van 2 miljoen rijen zijn.

Nu, ik wist dat dit gebeurde. De reden dat ik deze weg insloeg was echter omdat de "juiste" manier om dit te doen, het strikt gebruiken van JOIN's, nog meer tijd in beslag nam. Dit komt door de vervelende chaos die nodig is om te bepalen wie de manager van een bepaalde ploeg is. Ik moet me aansluiten bij shift_times om erachter te komen wat de juiste shift zelfs is, terwijl ik me tegelijkertijd aansluit bij shift_positions om het niveau van de gebruiker te achterhalen. Ik denk niet dat de MySQL-optimizer hier goed mee omgaat, en uiteindelijk een ENORME wangedrocht creëert van een tijdelijke tabel van de joins, en er vervolgens uit filtert wat niet van toepassing is.

Dus, omdat de afgeleide tabel de "manier om te gaan" leek, hield ik hier een tijdje koppig mee vol. Ik heb geprobeerd het in een JOIN-clausule te plaatsen, geen verbetering. Ik heb geprobeerd een tijdelijke tabel te maken met de afgeleide tabel erin, maar nogmaals, het was te traag omdat de tijdelijke tabel niet was geïndexeerd.

Ik kwam tot het besef dat ik deze berekening van ploegen, tijden en posities verstandig moest aanpakken. Ik dacht, misschien zou een VIEW de juiste keuze zijn. Wat als ik een VIEW heb gemaakt dat deze informatie bevat:(shop_id, shift_id, dow, start, end, manager_id). Dan zou ik gewoon via shop_id aan de kaartjestafel moeten deelnemen en de hele DAYOFWEEK/TIME-berekening, en ik zou in zaken zijn. Natuurlijk heb ik me niet herinnerd dat MySQL VIEW's nogal gemakkelijk afhandelt. Het realiseert ze helemaal niet, het voert gewoon de zoekopdracht uit die u zou hebben gebruikt om de weergave voor u te krijgen. Dus door tickets hieraan te koppelen, voerde ik in wezen mijn oorspronkelijke vraag uit - geen verbetering.

Dus in plaats van een VIEW besloot ik een TIJDELIJKE TAFEL te gebruiken. Dit werkte goed als ik slechts één van de managers tegelijk ophaalde (gemaakt of opgelost), maar het was nog steeds behoorlijk traag. Ik kwam er ook achter dat je met MySQL niet twee keer naar dezelfde tabel kunt verwijzen in dezelfde zoekopdracht (ik zou twee keer aan mijn tijdelijke tabel moeten deelnemen om onderscheid te kunnen maken tussen manager_created en manager_resolved). Dit is een grote WTF, aangezien ik het kan doen zolang ik "TIJDELIJK" niet specificeer - dit is waar de CREATE TABLE magic ENGINE=MEMORY in het spel kwam.

Met deze pseudo-tijdelijke tabel in de hand probeerde ik mijn JOIN voor just manager_created opnieuw. Het presteerde goed, maar nog steeds vrij traag. Maar toen ik weer lid werd om manager_resolved te krijgen in dezelfde query, tikte de querytijd terug in de stratosfeer. Als we naar de EXPLAIN kijken, wordt de volledige tafelscan van tickets (rijen ~ 2 mln) getoond, zoals verwacht, en de JOIN's op de magische tafel op ~ 2.087 elk. Nogmaals, ik leek tegen een mislukking aan te lopen.

Ik begon nu na te denken over hoe ik de JOIN's helemaal kon vermijden en toen vond ik een obscure oude message board-post waarin iemand voorstelde om subselecties te gebruiken (kan de link niet vinden in mijn geschiedenis). Dit is wat leidde tot de tweede SELECT-query die hierboven is weergegeven (de tickets_extra creatie). In het geval van het selecteren van slechts één managerveld, presteerde het goed, maar nogmaals, met beide was het onzin. Ik keek naar de UITLEG en zag dit:

*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: t
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 173825
        Extra: 
*************************** 2. row ***************************
           id: 3
  select_type: DEPENDENT SUBQUERY
        table: m
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 2037
        Extra: Using where
*************************** 3. row ***************************
           id: 2
  select_type: DEPENDENT SUBQUERY
        table: m
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 2037
        Extra: Using where
3 rows in set (0.00 sec)

Ack, de gevreesde AFHANKELIJKE SUBQUERY. Het wordt vaak aangeraden om deze te vermijden, omdat MySQL ze meestal op een outside-in manier uitvoert, waarbij de inner query voor elke rij van de outer wordt uitgevoerd. Ik negeerde dit en vroeg me af:"Nou... wat als ik deze stomme magische tafel gewoon indexeerde?". Zo werd de ADD-index (shop_id, dow) geboren.

Bekijk dit eens:

mysql> CREATE TABLE magic ENGINE=MEMORY
<snip>
Query OK, 3220 rows affected (0.40 sec)

mysql> ALTER TABLE magic ADD INDEX (shop_id, dow);
Query OK, 3220 rows affected (0.02 sec)

mysql> CREATE TABLE tickets_extra ENGINE=MyISAM
<snip>
Query OK, 1933769 rows affected (24.18 sec)

mysql> drop table magic;
Query OK, 0 rows affected (0.00 sec)

Nu DAT IS waar ik het over heb!

Conclusie

Dit is zeker de eerste keer dat ik een niet-TIJDELIJKE tabel heb gemaakt en deze meteen heb INDEXEERD, gewoon om een ​​enkele zoekopdracht efficiënt uit te voeren. Ik denk dat ik er altijd van uitging dat het toevoegen van een index on-the-fly een onbetaalbare operatie is. (Het toevoegen van een index aan mijn ticketstabel van 2 mln rijen kan meer dan een uur duren). Maar voor slechts 3.000 rijen is dit een makkie.

Wees niet bang voor AFHANKELIJKE SUBQUERIES, het creëren van TIJDELIJKE tabellen die dat niet zijn, het indexeren ter plekke of buitenaardse wezens. Het kunnen allemaal goede dingen zijn in de juiste situatie.

Bedankt voor alle hulp StackOverflow. :-D



  1. Waarom moeten we de MySQL-database sluiten na een query-opdracht?

  2. Ontdek de geschiedenis van SQL-query's

  3. Ik probeer een bestand te kopiëren, maar krijg een foutmelding

  4. Fix "ERROR 1136 (21S01):Kolomtelling komt niet overeen met waardetelling op rij 1" bij het invoegen van gegevens in MySQL