Запрос Mysql выполняется очень медленно

#mysql #sql #query-optimization #groupwise-maximum

Вопрос:

У меня есть запрос, который я использую в своей модели CodeIgniter для получения количества списков товаров между определенными днями. Это прекрасно работает, когда в моей таблице меньше элементов, но в моей таблице более 100 000 записей, и чтобы просто получить результат за 2 дня, требуется около 3-4 минут. Чем дольше дни » от » и » до » отделены друг от друга, тем больше времени это занимает.

Вот запрос: (Dbfiddle:https://dbfiddle.uk/?субд=mysql_8.0amp;fiddle=e7a99f08ecd217cbeb09fe6676cfe645)

 with Y as (
  with recursive D (n, day) as (
    select 1 as n, '2021-09-25' my_date
    union
    select n 1, day   interval 1 day from D
      where day   interval 1 day < '2021-10-15'
  ) select * from D
), X as (
  select Y.day,
         l.*,
         (select status_from from logs
            where logs.refno = l.refno
              and logs.logtime >= Y.day
            order by logs.logtime
            limit 1) logstat
    from listings l, Y
    where l.added_date <= Y.day
), Z as (
  select X.day, ifnull(X.logstat,X.status) stat_day, count(*) cnt
    from X
    group by X.day, stat_day
)
select Z.day,
  sum(case when Z.stat_day = 'D' then Z.cnt else 0 end ) Draft,
  sum(case when Z.stat_day = 'A' then Z.cnt else 0 end ) Action,
  sum(case when Z.stat_day = 'Y' then Z.cnt else 0 end ) Publish,
  sum(case when Z.stat_day = 'S' then Z.cnt else 0 end ) Sold,
  sum(case when Z.stat_day = 'L' then Z.cnt else 0 end ) Let
  from Z
  group by Z.day
  order by Z.day;
 

По сути, этот запрос выполняет status_from из журналов, где дата указана во время и после выбранного диапазона дат, и извлекает added_date из списков, где дата совпадает с диапазоном дат, выбранным пользователем, и вычисляет его. Как только он извлек эти записи, он проверяет таблицу на предмет того, какую переменную содержит этот статус, и выполняет a sum(case when else 0) , чтобы получить общее количество.

Одна из причин, по которой я думаю, что запрос медленный, заключается в том, что он должен вычислять сумму для статусов в самом запросе, поэтому, возможно, было бы быстрее выполнить подсчет на стороне php? Если да, то как я могу создать инструкцию для итерации подсчета в моем классе представления.

Текущий Класс Представления:

 <?php
            foreach($data_total as $row ){
               $draft = $row->draft ? $row->draft : 0;
               $publish = $row->publish ? $row->publish : 0;
               $action = $row->action ? $row->action : 0;
               $sold = $row->sold ? $row->sold : 0;
               $let = $row->let ? $row->let : 0;                              
          ?>
              <tr>
                    <td><?= $row->day?></td>
                    <td><?= $draft ?></td>
                    <td><?= $publish ?></td>
                    <td><?= $action ?></td>
                    <td><?= $sold ?></td>
                    <td><?= $let ?></td>
              </tr>
          <?php }  ?>
 

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

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

1. Вам необходимо включить результаты объяснения для вашего запроса, определения таблиц, включая индексы, для всех затронутых таблиц в запросе. Без них довольно сложно ответить на ваш вопрос!

2. Мое первое предположение состоит в том, чтобы добавить индексы к двум столбцам с метками ВРЕМЕНИ, которые фильтруются и/или сортируются: dbfiddle.uk/…

3. Вам нужно показать нам определения таблиц и индексов , а также количество строк для каждой из таблиц. Возможно, ваши таблицы плохо определены. Возможно, индексы созданы неправильно. Может быть, у вас нет индекса по той колонке, о которой вы думали. Не видя определений таблиц и индексов, мы не можем этого сказать. Нам нужно количество строк, потому что это может повлиять на планирование запросов. Если вы знаете, как выполнить EXPLAIN или получить план выполнения, также укажите результаты в вопросе. Если у вас нет индексов, посетите use-the-index-luke.com .

4. Начнем с ALTER TABLE listings ADD INDEX added_date (added_date) этого . Затем ОБЪЯСНИТЕ или ОБЪЯСНИТЕ АНАЛИЗ в вашей системе с помощью ваших реальных данных (а не крошечного подмножества в dbfiddle) и покажите нам результат.

5. @PeterTrcka да, я пробовал, и все равно требуется 3-4 минуты, чтобы вернуть 2 записи

Ответ №1:

Это быстрее? Если вы вызываете запрос чаще, вы можете рассмотреть возможность сохранения ROW_NUMBER в logs таблицу

 with calendar as (
with recursive cal (n, day) as (
    select 1 as n, '2021-09-25' my_date
    union
    select n 1, day   interval 1 day from cal
    where day   interval 1 day < '2021-10-15'
    )select * from cal
), loggs as (
    select
         ROW_NUMBER() OVER (partition by refno order by logtime) as RN
        ,status_from as logstat
        ,refno
        ,logtime
    from logs
),X as (
  select cal.day,
         l.*,
         logs.logstat,
         RN,
         min(RN) over (partition by l.refno, cal.day) as RN_MIN
    from listings l
    join calendar as cal on l.added_date <= cal.day
    left join loggs as logs on logs.refno = l.refno and logs.logtime >= cal.day
), Z as (
  select X.day, ifnull(X.logstat,X.status) stat_day, count(*) cnt
    from X
    where ifnull(RN, 0) = ifnull(RN_min, 0)
    group by X.day, stat_day
)
select Z.day,
  sum(case when Z.stat_day = 'D' then Z.cnt else 0 end ) Draft,
  sum(case when Z.stat_day = 'A' then Z.cnt else 0 end ) Action,
  sum(case when Z.stat_day = 'Y' then Z.cnt else 0 end ) Publish,
  sum(case when Z.stat_day = 'S' then Z.cnt else 0 end ) Sold,
  sum(case when Z.stat_day = 'L' then Z.cnt else 0 end ) Let
  from Z
  group by Z.day
  order by Z.day;
 

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

1. Эй, было бы возможно, если бы вы могли показать это мне в предоставленном dbfiddle, так как я попробовал это в скрипке, и это дало некоторые ошибки dbfiddle.uk/…

2. Работает на меня. играть на скрипке .

Ответ №2:

Я упростил ваш запрос, но я не уверен, что вы получите значительное улучшение во времени выполнения. Необходимо определить подходящие индексы.

Пожалуйста, внимательно проверьте его и убедитесь, что он правильно выведен.

 WITH RECURSIVE 
  cal AS (SELECT '2021-09-25' AS day
    
          UNION ALL
    
          SELECT day   interval 1 day 
          FROM cal
          WHERE day   interval 1 day < '2021-10-15'),
  
  X AS (SELECT DISTINCT
                  cal.day,
                  l.id,
                  l.status,
                  FIRST_VALUE(status_from) OVER (PARTITION BY logs.refno, cal.day ORDER BY logs.logtime) AS logstat
        FROM listings l
        INNER JOIN cal ON l.added_date <= cal.day
        LEFT JOIN logs ON logs.refno = l.refno AND logs.logtime >= cal.day)

SELECT X.day,
       COUNT(CASE WHEN IFNULL(X.logstat, X.status) = 'D' THEN 1 END) Draft,
       COUNT(CASE WHEN IFNULL(X.logstat, X.status) = 'A' THEN 1 END) Action,
       COUNT(CASE WHEN IFNULL(X.logstat, X.status) = 'Y' THEN 1 END) Publish,
       COUNT(CASE WHEN IFNULL(X.logstat, X.status) = 'S' THEN 1 END) Sold,
       COUNT(CASE WHEN IFNULL(X.logstat, X.status) = 'L' THEN 1 END) Let
FROM X
GROUP BY X.day
ORDER BY X.day;
 

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

1. Это не улучшило время выполнения, но я считаю, что проблема в том, что я не создаю индексы для таблицы списков.

2. Спасибо @JJM50!!! Я думаю, что индексы могут вам помочь. Запрос генерирует много строк, возможно, вы можете каким-то образом ограничить их.

3. Итак , теперь предположим, что я создаю индекс с помощью ALTER TABLE listings ADD INDEX added_date (added_date) added_date, как я могу получить доступ к этому индексу в этом запросе?

4. MySql будет использовать его, когда сочтет нужным, но вы также можете принудительно использовать индекс. Вы можете использовать индекс и подсказки для индексов.


Ответ №3:

Если ваш конечный результат будет размещен на веб-сайте, снимок данных обычно является лучшей практикой, чем прямая трансляция прошлых действий. В прошлом я использовал хранимые процедуры для ежедневного обновления таблицы с прошлыми действиями, а затем использовал представление для выбора прошлых действий, объединенных с текущими действиями, чтобы уменьшить время загрузки для моих зрителей.

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

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