sql >> Databáze >  >> RDS >> Mysql

Jak mohu dále optimalizovat odvozený tabulkový dotaz, který funguje lépe než ekvivalent JOINed?

No, našel jsem řešení. Vyžadovalo to spoustu experimentů a myslím, že i trochu slepého štěstí, ale tady to je:

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;

Dlouhé vysvětlení

Nyní vysvětlím, proč to funguje, a můj příbuzný proces a kroky, jak se sem dostat.

Za prvé, věděl jsem, že dotaz, který jsem zkoušel, trpí kvůli obrovské odvozené tabulce a následným připojením k této tabulce. Vzal jsem si svou dobře indexovanou tabulku tiketů a spojil do ní všechna data shift_times, pak jsem to nechal žvýkat MySQL, zatímco se pokoušelo připojit k tabulce shifts a shift_positions. Toto odvozené monstrum by bylo až 2 miliony řádků bez indexu.

Teď jsem věděl, že se to děje. Důvod, proč jsem šel touto cestou, byl ten, že „správný“ způsob, jak to udělat, striktně používat JOINy, zabral ještě delší dobu. To je způsobeno ošklivým trochou chaosu, který je nutný k určení toho, kdo je vedoucím dané směny. Musím se připojit k shift_times, abych zjistil, jaký je správný posun, a zároveň se připojit k shift_positions, abych zjistil úroveň uživatele. Nemyslím si, že to optimalizátor MySQL zvládá příliš dobře a nakonec vytvoří OBROVSKOU příšernost dočasné tabulky spojení a poté odfiltruje to, co neplatí.

Takže, protože odvozená tabulka se zdála být „cestou“, chvíli jsem v tom tvrdošíjně setrval. Zkoušel jsem to vložit do klauzule JOIN, žádné zlepšení. Pokusil jsem se vytvořit dočasnou tabulku s odvozenou tabulkou, ale opět to bylo příliš pomalé, protože dočasná tabulka nebyla indexována.

Uvědomil jsem si, že tento výpočet posunu, časů, pozic musím zvládnout rozumně. Říkal jsem si, že možná POHLED by byla ta správná cesta. Co kdybych vytvořil VIEW, který by obsahoval tyto informace:(id_obchodu, id_směny, dow, začátek, konec, id_manažera). Pak bych se jednoduše musel připojit k tabulce vstupenek podle shop_id a celého výpočtu DAYOFWEEK/TIME a mohl bych podnikat. Samozřejmě jsem si nevzpomněl, že MySQL zachází s VIEWy poměrně snadno. Vůbec je nezhmotní, jednoduše spustí dotaz, který byste použili k získání pohledu za vás. Takže připojením lístků k tomuto jsem v podstatě spustil svůj původní dotaz - žádné zlepšení.

Takže místo VIEW jsem se rozhodl použít TEMPORARY TABLE. Fungovalo to dobře, pokud jsem najednou stáhl pouze jednoho z manažerů (vytvořených nebo vyřešených), ale stále to bylo docela pomalé. Také jsem zjistil, že s MySQL nemůžete odkazovat na stejnou tabulku dvakrát ve stejném dotazu (musel bych se ke své dočasné tabulce připojit dvakrát, abych mohl rozlišovat mezi manager_created a manager_resolved). To je velké WTF, protože to můžu dělat, pokud neuvedu "TEMPORARY" - tady vstoupil do hry magický ENGINE=MEMORY CREATE TABLE.

S touto pseudo dočasnou tabulkou v ruce jsem znovu zkusil svůj JOIN pro just manager_created. Fungovalo to dobře, ale stále dost pomalu. Přesto, když jsem se znovu PŘIPOJIL, abych dostal manager_resolved ve stejném dotazu, čas dotazu se vrátil zpět do stratosféry. Pohled na EXPLAIN ukázal kompletní skenování lístků u stolu (řádky ~2mln), jak se očekávalo, a JOINy ​​na magický stůl za ~2,087 každý. Opět se zdálo, že se dostávám do selhání.

Nyní jsem začal přemýšlet o tom, jak se JOINům úplně vyhnout, a tehdy jsem našel nějaký obskurní starodávný příspěvek na nástěnce, kde někdo navrhoval použití podvýběrů (nemohu najít odkaz v mé historii). To vedlo k druhému SELECT dotazu uvedenému výše (vytvoření ticket_extra). V případě výběru pouze jednoho manažerského pole to fungovalo dobře, ale opět u obou to byla kravina. Podíval jsem se na EXPLAIN a viděl jsem toto:

*************************** 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, obávaný ZÁVISLÝ SUBQUERY. Často se doporučuje se jim vyhnout, protože MySQL je obvykle provede způsobem zvenčí-in, přičemž vnitřní dotaz provede pro každý řádek vnějšího. Ignoroval jsem to a přemýšlel jsem:"No... co kdybych právě zaindexoval tuhle pitomou kouzelnou tabulku?". Tak se zrodil index ADD (shop_id, dow).

Podívejte se na toto:

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)

Nyní TO JE o čem to mluvím!

Závěr

Toto je rozhodně poprvé, co jsem za chodu vytvořil tabulku, která není DOČASNÁ, a INDEXOVÁM ji za chodu, jednoduše proto, abych efektivně provedl jediný dotaz. Myslím, že jsem vždy předpokládal, že přidávání indexu za běhu je neúměrně drahá operace. (Přidání indexu do tabulky mých lístků s 2 miliony řádků může trvat déle než hodinu). Přesto je to pro pouhé 3000 řádků hračka.

Nebojte se ZÁVISLÝCH SUBQUERIES, vytváření DOČASNÝCH tabulek, které ve skutečnosti nejsou, indexování za chodu nebo mimozemšťanů. Všechny mohou být dobré věci ve správné situaci.

Děkujeme za veškerou pomoc StackOverflow. :-D



  1. Nahrajte obrázek na server a uložte cestu obrázku do databáze mysql

  2. Jak funguje funkce EXPORT_SET() v MySQL

  3. Správný způsob zpracování obousměrných 1:m v Green-DAO

  4. Jak znovu vytvořit smazanou tabulku pomocí Django Migrations?