Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Jak jeszcze bardziej zoptymalizować zapytanie tabeli pochodnej, które działa lepiej niż odpowiednik JOINed?

Cóż, znalazłem rozwiązanie. Wymagało to wielu eksperymentów i myślę, że trochę ślepego szczęścia, ale oto jest:

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;

Długie wyjaśnienie

Teraz wyjaśnię, dlaczego to działa, i mój względny proces i kroki, aby się tutaj dostać.

Po pierwsze, wiedziałem, że zapytanie, które próbowałem, ucierpiało z powodu ogromnej tabeli pochodnej i kolejnych dołączeń do niej. Wziąłem moją dobrze zindeksowaną tabelę biletów i dołączyłem do niej wszystkie dane shift_times, a następnie pozwoliłem MySQLowi żuć to, próbując dołączyć do tabeli shifts i shift_positions. Ten pochodny behemot miałby nawet 2 miliony wierszy bez indeksowania.

Teraz wiedziałem, że to się dzieje. Powodem, dla którego poszedłem tą drogą, było to, że „właściwy” sposób na zrobienie tego, użycie ściśle JOIN, zabierał jeszcze więcej czasu. Wynika to z paskudnego chaosu wymaganego do ustalenia, kto jest kierownikiem danej zmiany. Muszę dołączyć do shift_times, aby dowiedzieć się, jaka jest właściwa zmiana, jednocześnie dołączam do shift_positions, aby dowiedzieć się, jaki jest poziom użytkownika. Nie sądzę, aby optymalizator MySQL radził sobie z tym zbyt dobrze i ostatecznie tworzy OGROMNĄ potworność tymczasowej tabeli złączeń, a następnie odfiltrowuje to, co nie ma zastosowania.

Tak więc, ponieważ tabela pochodna wydawała się być „drogą do zrobienia”, uparcie trwałem przy tym przez chwilę. Próbowałem umieścić to w klauzuli JOIN, bez poprawy. Próbowałem utworzyć tabelę tymczasową z tabelą pochodną, ​​ale znowu było to zbyt wolne, ponieważ tabela tymczasowa nie była indeksowana.

Zdałem sobie sprawę, że muszę rozsądnie poradzić sobie z obliczeniami przesunięcia, czasów, pozycji. Pomyślałem, że może VIEW będzie dobrym rozwiązaniem. Co jeśli utworzyłem WIDOK, który zawierał te informacje:(id_sklepu, identyfikator_zmiany, dow, początek, koniec, identyfikator_menedżera). Wtedy musiałbym po prostu dołączyć do stolika z biletami przez identyfikator_sklepu i cały DZIEŃ TYGODNIA/CZAS i byłbym w biznesie. Oczywiście nie pamiętam, że MySQL radzi sobie raczej z VIEWami. W ogóle ich nie materializuje, po prostu uruchamia zapytanie, którego użyłbyś do uzyskania widoku. Więc dołączając do tego bilety, zasadniczo uruchamiałem moje oryginalne zapytanie - bez poprawy.

Dlatego zamiast WIDOKU zdecydowałem się na TABELĘ TYMCZASOWĄ. Działało to dobrze, gdybym pobierał tylko jednego z menedżerów (utworzony lub rozwiązany) na raz, ale nadal był dość powolny. Odkryłem również, że w MySQL nie można odwoływać się do tej samej tabeli dwa razy w tym samym zapytaniu (musiałbym dwukrotnie dołączyć do mojej tabeli tymczasowej, aby móc odróżnić między manager_created a manager_resolved). To jest duży WTF, ponieważ mogę to zrobić, o ile nie określę „TYMCZASOWY” - w tym miejscu pojawiła się magiczna funkcja CREATE TABLE ENGINE=MEMORY.

Z tą pseudotymczasową tabelą w ręku ponownie spróbowałem mojego JOIN dla właśnie manager_created. Sprawdziło się dobrze, ale nadal dość wolno. Jednak, kiedy dołączyłem ponownie, aby uzyskać menedżer_rozwiązany w tym samym zapytaniu, czas zapytania powrócił do stratosfery. Spojrzenie na EXPLAIN pokazało pełny skan biletów (wiersze ~2mln), zgodnie z oczekiwaniami, oraz JOIN na magicznym stole po ~2.087 każdy. Znowu wydawało mi się, że zaczynam ponosić porażkę.

Zacząłem teraz myśleć o tym, jak całkowicie uniknąć JOIN i właśnie wtedy znalazłem jakiś niejasny starożytny post na forum, w którym ktoś zasugerował użycie subselekcji (nie mogę znaleźć linku w mojej historii). To właśnie doprowadziło do drugiego zapytania SELECT pokazanego powyżej (czyli do tworzenia biletów_dodatkowych). W przypadku wybrania tylko jednego pola menedżerskiego wypadło to dobrze, ale znowu z obydwoma to było gówniane. Spojrzałem na WYJAŚNIENIE i zobaczyłem to:

*************************** 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, przerażające PODPYTANIE ZALEŻNE. Często sugeruje się ich unikanie, ponieważ MySQL zwykle wykonuje je na zewnątrz, wykonując wewnętrzne zapytanie dla każdego wiersza zewnętrznego. Zignorowałem to i zacząłem się zastanawiać:„No cóż… a jeśli właśnie zindeksowałem tę głupią tabelę magiczną?”. W ten sposób narodził się indeks ADD (shop_id, dow).

Sprawdź to:

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)

Teraz TO JEST o czym mówię!

Wniosek

Jest to zdecydowanie pierwszy raz, kiedy stworzyłem w locie tabelę nietymczasową i zindeksowałem ją w locie, aby po prostu wykonać jedno zapytanie. Wydaje mi się, że zawsze zakładałem, że dodawanie indeksu w locie jest zbyt kosztowną operacją. (Dodanie indeksu 2mln wierszy do mojej tabeli biletów może zająć ponad godzinę). Jednak dla zaledwie 3000 rzędów jest to bułka z masłem.

Nie bój się ZALEŻNYCH PODZAPYTAŃ, tworzenia TYMCZASOWYCH tabel, których tak naprawdę nie ma, indeksowania w locie lub obcych. Wszystkie mogą być dobre w odpowiedniej sytuacji.

Dzięki za wszelką pomoc StackOverflow. :-D



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. JPA + Hibernate:Jak zdefiniować ograniczenie mające ON DELETE CASCADE

  2. Błąd krytyczny:nie można użyć obiektu typu stdClass jako tablicy w

  3. Błąd podczas wstawiania do bazy danych sql za pomocą php

  4. Zdarzenie MySQL nie działa

  5. mysqli insert - ale tylko jeśli nie jest to duplikat