Проблема производительности по запросу с математическими вычислениями

0

Этот мой запрос с его производительностью (slow_query_log):

SELECT j.'offer_id', o.'offer_name', j.'success_rate'
FROM
(
    SELECT 
        t.'offer_id',
        (
            SUM(CASE WHEN 'offer_id' = t.'offer_id' AND 'sales_status' = 'SUCCESS' THEN 1 ELSE 0 END) / COUNT(*)
        ) AS 'success_rate'
    FROM 'tblSales' AS t
    WHERE   DATE(t.'sales_time') = CURDATE()  
    GROUP BY t.'offer_id'               
    ORDER BY 'success_rate' DESC
) AS j
LEFT JOIN 'tblOffers' AS o
    ON j.'offer_id' = o.'offer_id'
LIMIT 5;

# Time: 180113 18:51:19
# User@Host: root[root] @ localhost [127.0.0.1]  Id:    71
# Query_time: 10.472599  Lock_time: 0.001000 Rows_sent: 0  Rows_examined: 1156134

Здесь tblOffers имеют все ПРЕДЛОЖЕНИЯ, перечисленные. И tblSales содержит все продажи. То, что я пытаюсь выяснить, - это самые продаваемые предложения, основанные на коэффициенте успеха (т.е. Продажах, которые являются УСПЕХОМ).

Запрос работает отлично и предоставляет требуемый результат. Но, похоже, он немного медленнее.

offer_id и sales_status уже проиндексированы в tblSales. Итак, есть ли у вас предложения по улучшению внутреннего запроса (где он вычисляет коэффициент успеха), чтобы производительность могла быть улучшена? Я играю с математикой более двух часов. Но не мог лучше.

tblSales, tblSales имеет много данных. Он содержит те продажи, которые являются УСПЕШНЫМИ, НЕИСПРАВНОСТЬЮ, ОТКАЗОМ и т.д.

Спасибо


РЕДАКТИРОВАТЬ

По мере того как вы просили включить в него также дизайн таблицы (включены только соответствующие поля):

tblSales
'sales_id'          bigint UNSIGNED NOT NULL AUTO_INCREMENT,
'offer_id'          bigint UNSIGNED NOT NULL DEFAULT '0',   
'sales_time'        DATETIME NOT NULL DEFAULT  '0000-00-00 00:00:00',   
'sales_status'      ENUM('WAITING', 'SUCCESS', 'FAILED', 'CANCELLED') NOT NULL DEFAULT 'WAITING',
PRIMARY KEY ('sales_id'),
KEY ('offer_id'),
KEY ('sales_status')

В этой таблице есть и другие поля, в которых содержится другая информация. Сумма, user_id и т.д., Которые не имеют отношения к моему вопросу.

  • 1
    Пожалуйста, опубликуйте схему таблицы и результаты запроса EXPLAIN чтобы мы могли предоставить вам лучшее решение. Также будет полезна ссылка на sqlfiddle.
  • 0
    Кстати, LIMIT без ORDER BY довольно бессмысленно
Показать ещё 1 комментарий
Теги:
performance

4 ответа

0
Лучший ответ

Здесь tblOffers имеют все ПРЕДЛОЖЕНИЯ, перечисленные. И tblSales содержит все продажи. То, что я пытаюсь выяснить, - это самые продаваемые предложения, основанные на коэффициенте успеха (т.е. Продажах, которые являются УСПЕХОМ).

Подходите к этому простым JOIN и GROUP BY:

SELECT s.offer_id, o.offer_name,
       AVG(s.sales_status = 'SUCCESS') as success_rate
FROM tblSales s JOIN
     tblOffers o
     ON o.offer_id = s.offer_id
WHERE s.sales_time >= CURDATE() AND
      s.sales_time < CURDATE() + INTERVAL 1 DAY
GROUP BY s.offer_id, o.offer_name              
ORDER BY success_rate DESC;

Заметки:

  • Использование арифметики дат позволяет запросу использовать индекс в tblSales(sales_time) - или еще лучше tblSales(salesTime, offer_id, sales_status).
  • Арифметика для success_rate была упрощена, хотя это имеет минимальное влияние на производительность.
  • Я добавил offer_name в группу GROUP BY. Если вы изучаете SQL, у вас всегда должны быть все неагрегированные ключи в предложении GROUP BY.
  • LEFT JOIN необходим, только если у вас есть предложения в tblSales которые не находятся в tblOffers. Я предполагаю, что у вас есть правильные отношения с внешним ключом, и это не так.
  • 0
    Оптимизация для LIMIT 5 также необходима.
  • 0
    Спасибо. Я попробовал это с добавлением INDEX в поле sales_time и теперь, кажется, лучше. Но только сомнение, у primary_key=sales_id уже есть primary_key=sales_id , затем проиндексированные поля offer_id , sales_status , а теперь sales_times . Но этот tblSales будет добавлять новые записи очень часто. Я имею в виду в течение минуты, может быть, от 100 до 1000 записей. Так не затруднит ли все эти проиндексированные поля вставку и обновление записей в этой таблице? На данный момент у меня есть около 1156134 строк в этой таблице, и она растет.
Показать ещё 2 комментария
1

Многочисленные "проблемы", ни одна из которых не связана с "математикой".

JOINs Сложно. LEFT JOIN говорит: "Мне все равно, существует ли строка в таблице" справа "(я подозреваю, что вам не нужно LEFT?), Но в ней также говорится:" В правой таблице может быть несколько строк. На основе имен столбцов, я предполагаю, что есть только один offer_name для каждого offer_id. Если это правильно, то вот моя первая рекомендация. (Это убедит Оптимизатора в том, что с JOIN нет проблемы.) Изменение из

SELECT ..., o.offer_name, ...
    LEFT JOIN  'tblOffers' AS o  ON j.'offer_id' = o.'offer_id'
    ...

в

SELECT ...,
        ( SELECT offer_name FROM tbloffers WHERE offer_id j.offer_id
        ) AS offer_name, ...

Он также избавляется от ошибки, в которой вы предполагаете, что внутренний ORDER BY будет сохранен для LIMIT. Это имело место, но в новых версиях MariaDB/MySQL это не так. ORDER BY в "производной таблице" (ваш подзапрос) теперь игнорируется.

2 вниз, еще несколько.

"Не скрывайте индексированный столбец в функции". Я имею в виду DATE(t.sales_time) = CURDATE(). Предполагая, что у вас нет значений sales_time для "будущего", тогда этот тест можно изменить на t.sales_time >= CURDATE(). Если вам действительно нужно ограничиться только сегодня, сделайте следующее:

  AND sales_time >= CURDATE()
  AND sales_time  < CURDATE() + INTERVAL 1 DAY

ORDER BY и LIMIT обычно должны быть объединены. В вашем случае вы также можете добавить LIMIT в "производную таблицу", тем самым приводя к 5 строкам для внешнего запроса. Но... Есть еще вопрос, как правильно их отсортировать. Итак, измените

 SELECT ...
     FROM ( SELECT ...
               ORDER BY ... )
     LIMIT ...

в

 SELECT ...
     FROM ( SELECT ...
               ORDER BY ...
               LIMIT 5 )    -- trim sooner
     ORDER BY ...           -- deal with the loss of ordering from derived table

Продвигаясь все вместе, у меня есть

SELECT  j.'offer_id', 
        ( SELECT  offer_name
            FROM  tbloffers
            WHERE  offer_id = j.offer_id 
        ) AS offer_name,
        j.'success_rate'
    FROM  
        ( SELECT  t.'offer_id',
                  AVG(t.sales_status = 'SUCCESS') AS 'success_rate'
            FROM  'tblSales' AS t
            WHERE  t.sales_time >= CURDATE()
            GROUP BY  t.'offer_id'
            ORDER BY  'success_rate' DESC
            LIMIT  5 
        ) AS j
    ORDER BY  'success_rate' DESC;

(Я взял на себя смелость сократить SUM(...) двумя способами.)

Теперь для индексов...

tblSales нужно как минимум (sales_time), но отпустите "покрытие" (sales_time):

INDEX(sales_time, sales_status, order_id)

Если tbloffers имеет PRIMARY KEY(offer_id), то дальнейший индекс не стоит добавлять. Иначе добавьте этот индекс покрытия (в этом порядке):

INDEX(offer_id, offer_name)

(Извиняюсь перед другими Ответчиками, я украл некоторые из ваших идей.)

  • 0
    Спасибо. Кажется, работает нормально. Но небольшая опечатка в вашем коде - это t.sales_status вместо s.sales_status . Возможно, вы перепутали письмо, которое я использовал для псевдонима. И спасибо за подсказку по limit
  • 0
    t.sales_status исправлено.
Показать ещё 1 комментарий
0

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

WHERE   DATE(t.'sales_time') = CURDATE()

Попробуйте изменить это на что-то похожее

Where t.sales_time >= @12-midnight-of-current-date and t.sales_time <= @23:59:59-of-current-date
  • 0
    Два ответа показывают «лучший» способ сделать этот диапазон времени и даты.
  • 0
    Спасибо за предложение @Goose
0

Основываясь на небольшой информации, которую вы предоставили (я имею в виду схему таблицы), вы можете попробовать следующее.

SELECT 'o'.'offer_id', 'o'.'offer_name', SUM(CASE WHEN 't'.'sales_status' = 'SUCCESS' THEN 1 ELSE 0 END) AS 'success_rate'
FROM 'tblOffers' 'o'
INNER JOIN 'tblSales' 't'
ON 'o'.'offer_id' = 't'.'offer_id'
WHERE DATE('t'.'sales_time') = CURDATE()  
GROUP BY 'o'.'offer_id'               
ORDER BY 'success_rate' DESC
LIMIT 0,5;

Вы можете найти образец этого запроса в этом примере SQL Fiddle

  • 1
    Спасибо @peterdarmis

Ещё вопросы

Сообщество Overcoder
Наверх
Меню