Mysql
 sql >> база данни >  >> RDS >> Mysql

Как мога да оптимизирам допълнително заявка за производна таблица, която работи по-добре от JOINed еквивалента?

Е, намерих решение. Отне много експерименти и мисля, че имам малко сляп късмет, но ето го:

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;

Дълго обяснение

Сега ще обясня защо това работи и моят роднина ще разгледа процеса и стъпките, за да стигна до тук.

Първо, знаех, че заявката, която опитвах, страда заради огромната извлечена таблица и последващите JOIN към това. Взех добре индексираната си таблица с билети и присъединявах към нея всички данни shift_times, след което оставях MySQL да я дъвче, докато се опитва да се присъедини към таблицата shift_times и shift_positions. Този извлечен бегемот би бил до 2 милиона реда неиндексирана бъркотия.

Сега знаех, че това се случва. Причината да вървя по този път обаче беше, че „правилният“ начин да направя това, използвайки стриктно JOIN, отнемаше още повече време. Това се дължи на гадния хаос, необходим, за да се определи кой е мениджърът на дадена смяна. Трябва да се присъединя към shift_times, за да разбера каква е дори правилната смяна, като същевременно се присъединя към shift_positions, за да разбера нивото на потребителя. Не мисля, че оптимизаторът на MySQL се справя много добре с това и в крайна сметка създава ОГРОМНО чудовище от временна таблица на съединенията, след което филтрира това, което не е приложимо.

Така че, тъй като получената таблица изглеждаше „начинът“, аз упорито упорито упорствах в това за известно време. Опитах се да го вкарам в клауза JOIN, без подобрение. Опитах се да създам временна таблица с извлечената таблица в нея, но отново беше твърде бавно, тъй като временната таблица беше неиндексирана.

Разбрах, че трябва да се справя разумно с това изчисление на смяна, времена, позиции. Мислех си, че може би VIEW ще бъде пътят. Ами ако създам ИЗГЛЕД, който съдържа тази информация:(shop_id, shift_id, dow, start, end, manager_id). Тогава просто ще трябва да се присъединя към таблицата с билети по shop_id и цялото изчисление ДЕН/СЕДМИЦА/ВРЕМЕТО и щях да работя. Разбира се, не успях да си спомня, че MySQL обработва VIEW доста лесно. Той изобщо не ги материализира, а просто изпълнява заявката, която бихте използвали, за да получите изгледа вместо вас. Така че, като присъединих билети към това, по същество изпълнявах първоначалната си заявка - без подобрение.

Така че вместо ИЗГЛЕД реших да използвам ВРЕМЕННА ТАБЛИЦА. Това работеше добре, ако извличах само един от мениджърите (създадени или разрешени) наведнъж, но все пак беше доста бавно. Освен това открих, че с MySQL не можете да се обърнете към една и съща таблица два пъти в една и съща заявка (ще трябва да се присъединя към моята временна таблица два пъти, за да мога да правя разлика между manager_created и manager_resolved). Това е голям WTF, тъй като мога да го направя, стига да не посоча "ВРЕМЕННО" - тук влезе в игра магията CREATE TABLE ENGINE=MEMORY.

С тази псевдо временна таблица в ръка опитах моя JOIN за току-що manager_created отново. Той се представи добре, но все пак доста бавно. И все пак, когато се присъединих отново, за да получа manager_resolved в същата заявка, времето на заявката се върна в стратосферата. Разглеждането на EXPLAIN показа пълното сканиране на таблицата на билети (редове ~2mln), както се очакваше, и JOIN на магическата маса при ~2087 всеки. Отново изглежда, че се сблъсквам с провал.

Сега започнах да мисля как изобщо да избягвам JOIN и тогава открих някаква неясна древна публикация на таблото за съобщения, където някой предложи да се използва подселекция (не мога да намеря връзката в моята история). Това доведе до втората SELECT заявка, показана по-горе (създаването на tickets_extra). В случай на избор само на едно поле на мениджър, той се представи добре, но отново и с двете беше гадно. Погледнах EXPLAIN и видях това:

*************************** 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, страшната ЗАВИСИМА ПОДЗАПЪРКА. Често се препоръчва да се избягват тези, тъй като MySQL обикновено ги изпълнява по външен начин, изпълнявайки вътрешната заявка за всеки ред от външната. Пренебрегнах това и се зачудих:"Е... ами ако просто индексирах тази глупава магическа таблица?". Така се роди индексът ADD (shop_id, dow).

Вижте това:

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)

СегаТОВА Е за какво говоря!

Заключение

Това определено е първият път, когато създадох не-ВРЕМЕННА таблица в движение и я ИНДЕКСИРАХ в движение, просто за да направя една-единствена заявка ефективно. Предполагам, че винаги съм предполагал, че добавянето на индекс в движение е твърде скъпа операция. (Добавянето на индекс към моята таблица с билети от 2 милиона реда може да отнеме повече от час). И все пак, само за 3000 реда това е cakewalk.

Не се страхувайте от ЗАВИСИМИ ПОДЗАПИТКИ, създаване на ВРЕМЕННИ таблици, които наистина не са, индексиране в движение или извънземни. Всички те могат да бъдат добри неща в правилната ситуация.

Благодаря за цялата помощ 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. MySQL комбинира две колони в една колона

  2. MySQL заявка с помощта на масив

  3. Промяна на набора от символи по подразбиране на MySQL на UTF-8 в my.cnf?

  4. Използване на наклонени черти след mysql_real_escape_string

  5. Защо да използвате MySQL над плоски файлове?