Оптимизация запросов MySQL с помощью временных таблиц

#mysql #sql

#mysql #sql

Вопрос:

РЕДАКТИРОВАТЬ Эти отдельные запросы являются частью более масштабной процедуры, которая используется для создания отчета в Excel, который может автоматически обновляться другими пользователями.

Я конвертирую запросы с SQL Server в MySQL. Приведенный ниже запрос содержит около 1,6 миллиона записей.

   CREATE TEMPORARY TABLE table_1
    AS
       (SELECT...
          FROM accounts a -- also a temporary table
              JOIN tdate d ON a.date1 <= d.date2
          WHERE PERIOD_DIFF(DATE_FORMAT(d.date2, '%Y%m'), DATE_FORMAT(a.date1, '%Y%m')) >= 0
         );
  

Затем он должен быть обновлен сам по себе и присоединен к самому себе. Из-за ограничений MySQL мне нужно дублировать таблицу два раза. UPDATE Именно здесь запрос начинает занимать много времени.

 
    /* Create duplicate temp tables */
    CREATE TEMPORARY TABLE table_2
    AS
       (SELECT * FROM table_1);

    CREATE TEMPORARY TABLE table_3
    AS
       (SELECT * FROM table_2);

    /* UPDATE table_1 */
        UPDATE table_1 a
        JOIN
           (SELECT ...
                   SUM(ind_costs) as tcosts
            FROM table_2 b
                JOIN table_3 s ON b.id = s.id
            WHERE b.dperiod >= s.dperiod
            GROUP BY b.id, 
                     b.dperiod) z
        ON a.id = z.id
                 AND a.dperiod = z.dperiod
    SET a.tcosts = z.tcosts;
  

Запрос отлично выполняется на SQL Server, я полагаю, потому что мне не нужно создавать 3 копии временной таблицы с 1,6 миллионами строк для UPDATE и JOIN самой себя. Этот запрос выполняется чрезвычайно медленно в MySQL. Есть предложения о том, как я мог бы оптимизировать это для более эффективной работы? Я работаю на сервере MySQL 5.7.30

РЕДАКТИРОВАТЬ Наконец, есть еще один запрос, который следует за UPDATE , который сужает область SELECT , JOIN содержит другую таблицу и фильтрует с помощью WHERE . Это используется в отчете, который экспортируется в Excel и может быть обновлен другими пользователями.

     DROP TEMPORARY TABLE IF EXISTS table_2;
    DROP TEMPORARY TABLE IF EXISTS table_3;

-- The final query results from the procedure
    SELECT  ...
    FROM table_1 a
        JOIN stats s ON a.state = s.state 
            AND a.column1 = s.column1
            AND a.column2 = s.column2
    WHERE a.dperiod = DATE_FORMAT(NOW(),'%Y%m')
        AND a.costs < s.avg_costs
    ORDER BY period DESC;
  

Выполнение запроса на SQL Server занимает несколько секунд, но я позволил ему выполняться более часа в MySQL, и он все еще не был завершен. Я ищу любые способы, которые потенциально могли бы ускорить весь процесс. Я понимаю, что это немного расплывчато. Заранее спасибо за ваши предложения.

РЕДАКТИРОВАНИЕ РЕШЕНИЯ Я опубликовал решение, которое ускорило выполнение запроса с более чем часа (я никогда не даю ему завершиться) до 4 минут. Хотя это и не идеально, это намного лучше, чем то, с чем я работал. Я был бы открыт для других предложений, если у людей есть улучшения, однако я выбрал то, что работало для меня на данный момент.

Комментарии:

1. Примеры данных и желаемые результаты действительно помогли бы — как и объяснение того, что должна делать логика.

2. Извините, я понял, что остановился на том, что мне нужно было затем запросить результаты после окончательного обновления для экспорта в отчет в Excel, поэтому запрос не был конечным компонентом.

Ответ №1:

Зачем вам вообще нужна временная таблица? Просто запустите один запрос. Я думаю, вы хотите:

 select t.*,
       sum(ind_costs) over (partition by id order by dperiod) as running_ind_costs
from table_1 t1;
  

Вы могли бы включить это в исходное определение таблицы.

Ответ №2:

Хотя это и не идеально, то, что я сделал до сих пор, сократило время выполнения запроса с более чем часа (никогда не позволяя ему выполняться полностью) примерно до 4 минут при использовании INDEX в указанном мной id столбце и в моей period колонке. Мой id столбец был уникальным для отдельных пользователей, а период представлял собой дату в '%Y%m' формате.

Я использовал этот метод, потому что это были обычные столбцы в предложениях JOIN , WHERE и GROUP BY . Я наткнулся на это, либо на составной, либо на покрывающий индекс, метод, считывающий ТАК и это.

Я создал индекс для каждой созданной мной временной таблицы, потому что не был уверен, перенесен ли индекс из каждой таблицы. Когда я пытался сделать это только на table_1 , это все еще было очень медленно, поэтому я сохранял их при каждом создании.

РЕДАКТИРОВАТЬ После тестирования мне нужно было только создать индекс для table_1 и для table_2 . Похоже, что индекс был скопирован из table_2 в table_3 .

 CREATE TEMPORARY TABLE table_1
    AS
       (SELECT...
          FROM accounts a -- also a temporary table
              JOIN tdate d ON a.date1 <= d.date2
          WHERE PERIOD_DIFF(DATE_FORMAT(d.date2, '%Y%m'), DATE_FORMAT(a.date1, '%Y%m')) >= 0
         );

/*
#############################################
added index here and for the first temp table
#############################################
*/
ALTER TABLE table_1
   ADD INDEX (id, reporting_period);
  
  /* Create duplicate temp tables */
    CREATE TEMPORARY TABLE table_2
    AS
       (SELECT * FROM table_1);

-- additional index
ALTER TABLE table_2
   ADD INDEX (placedetail_id, reporting_period);

    CREATE TEMPORARY TABLE table_3
    AS
       (SELECT * FROM table_2);

    /* UPDATE table_1 */
        UPDATE table_1 a
        JOIN
           (SELECT ...
                   SUM(ind_costs) as tcosts
            FROM table_2 b
                JOIN table_3 s ON b.id = s.id
            WHERE b.dperiod >= s.dperiod
            GROUP BY b.id, 
                     b.dperiod) z
        ON a.id = z.id
                 AND a.dperiod = z.dperiod
    SET a.tcosts = z.tcosts;

    DROP TEMPORARY TABLE IF EXISTS table_2;
    DROP TEMPORARY TABLE IF EXISTS table_3;
  
 -- The final query results from the procedure
    SELECT  ...
    FROM table_1 a
        JOIN stats s ON a.state = s.state 
            AND a.column1 = s.column1
            AND a.column2 = s.column2
    WHERE a.dperiod = DATE_FORMAT(NOW(),'%Y%m')
        AND a.costs < s.avg_costs
    ORDER BY period DESC;