sql >> Database teknologi >  >> RDS >> Mysql

Hvordan kan jeg yderligere optimere en afledt tabelforespørgsel, som yder bedre end den JOINed-ækvivalent?

Nå, jeg fandt en løsning. Det krævede en masse eksperimenter, og jeg tror en god smule blindt held, men her er det:

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;
 

Langvarig forklaring

Nu vil jeg forklare, hvorfor dette virker, og min pårørende gennemgår processen og trinene for at komme hertil.

For det første vidste jeg, at den forespørgsel, jeg prøvede, led på grund af den enorme afledte tabel og de efterfølgende JOINs til dette. Jeg tog min velindekserede billettabel og tilføjede alle shift_times-dataene til den, og lod derefter MySQL tygge på det, mens den forsøger at tilslutte mig shifts og shift_positions-tabellen. Denne afledte behemoth ville være op til et 2 millioner rækker uindekseret rod.

Nu vidste jeg, at det her skete. Grunden til, at jeg gik denne vej, var, at den "korrekte" måde at gøre dette på, ved at bruge strenge JOINs, tog endnu længere tid. Dette skyldes den grimme smule kaos, der kræves for at afgøre, hvem lederen af ​​et givent skift er. Jeg er nødt til at joine ned til shift_times for at finde ud af hvad det korrekte shift endda er, samtidig med at jeg går ned til shift_positions for at finde ud af brugerens niveau. Jeg tror ikke, at MySQL-optimeringsværktøjet håndterer dette særlig godt og ender med at skabe en KÆMPE monstrøsitet af en midlertidig tabel over joinforbindelserne, for derefter at filtrere det, der ikke gælder.

Så da den afledte tabel så ud til at være "vejen at gå", blev jeg stædigt ved i dette et stykke tid. Jeg prøvede at putte det ned i en JOIN-klausul, ingen forbedring. Jeg prøvede at oprette en midlertidig tabel med den afledte tabel i den, men igen var den for langsom, da den midlertidige tabel var uindekseret.

Jeg kom til at indse, at jeg var nødt til at håndtere denne beregning af skift, tider, stillinger fornuftigt. Jeg tænkte, måske ville en VIEW være vejen at gå. Hvad hvis jeg oprettede en VIEW, der indeholdt disse oplysninger:(shop_id, shift_id, dow, start, end, manager_id). Så skulle jeg simpelthen slutte mig til billettabellen ved shop_id og hele DAYOFWEEK/TIME-beregningen, og jeg ville være i gang. Selvfølgelig kunne jeg ikke huske, at MySQL håndterer VIEWs ret assitivt. Det virkeliggør dem slet ikke, det kører simpelthen den forespørgsel, du ville have brugt for at få visningen for dig. Så ved at tilføje billetter til dette, kørte jeg i det væsentlige min oprindelige forespørgsel - ingen forbedring.

Så i stedet for en VIEW besluttede jeg at bruge et MIDLERTIDIG TABEL. Dette fungerede godt, hvis jeg kun hentede en af ​​lederne (oprettet eller løst) ad gangen, men det var stadig ret langsomt. Jeg fandt også ud af, at med MySQL kan du ikke henvise til den samme tabel to gange i den samme forespørgsel (jeg ville være nødt til at tilslutte mig min midlertidige tabel to gange for at kunne skelne mellem manager_created og manager_resolved). Dette er en stor WTF, da jeg kan gøre det, så længe jeg ikke angiver "MIDLERTIDIG" - det var her, CREATE TABLE magic ENGINE=MEMORY kom i spil.

Med denne pseudo midlertidige tabel i hånden prøvede jeg min JOIN for netop manager_created igen. Det fungerede godt, men stadig ret langsomt. Men da jeg sluttede mig til igen for at få manager_resolved i den samme forespørgsel, tikkede forespørgselstiden tilbage i stratosfæren. Ved at kigge på EXPLAIN viste den fulde bordscanning af billetter (rækker ~2 mio.), som forventet, og JOIN'erne til det magiske bord på ~2.087 hver. Igen så det ud til, at jeg løb ind i fiasko.

Jeg begyndte nu at tænke på, hvordan jeg helt kunne undgå JOINs, og det var, da jeg fandt et obskurt gammelt opslagstavle-indlæg, hvor nogen foreslog at bruge undervalg (kan ikke finde linket i min historie). Dette er, hvad der førte til den anden SELECT-forespørgsel vist ovenfor (ticket_extra-oprettelsen). I tilfælde af at vælge kun et enkelt managerfelt, klarede det sig godt, men igen med begge var det lort. Jeg kiggede på EXPLAIN og så dette:

*************************** 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)

Ak, den frygtede AFHÆNGIGE SUBQUERY. Det foreslås ofte at undgå disse, da MySQL normalt udfører dem på en ekstern måde, og udfører den indre forespørgsel for hver række af den ydre. Jeg ignorerede dette og undrede mig:"Nå... hvad nu hvis jeg lige indekserede denne dumme magiske tabel?". Således blev ADD-indekset (shop_id, dow) født.

Tjek dette ud:

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 DET ER hvad jeg taler om!

Konklusion

Dette er helt klart første gang, jeg har oprettet en ikke-MIDLERTIDIG tabel i farten, og INDEKSERET den med det samme, simpelthen for at udføre en enkelt forespørgsel effektivt. Jeg har vel altid antaget, at tilføjelse af et indeks i farten er en uoverkommelig dyr operation. (At tilføje et indeks på min billettabel på 2 mio. rækker kan tage over en time). Men for blot 3.000 rækker er dette en cakewalk.

Vær ikke bange for AFHÆNGIGE UNDERSØGELSER, skabe MIDLERTIDIGE tabeller, der virkelig ikke er, indeksering i farten eller rumvæsener. De kan alle være gode ting i den rigtige situation.

Tak for al hjælpen StackOverflow. :-D



  1. Eksempler på JDBC-erklæringer – Indsæt, Slet, Opdater, Vælg Record

  2. Provisionering af MySQL/MariaDB Vault Database Secrets Engine med Terraform

  3. Du kan nu bruge Access med Microsoft Azure MFA!

  4. Tidligere Capgemini Executive, Sunitha Ray, slutter sig til ScaleGrid DBaaS for at udvide virksomhedens salg