使用数学计算查询的性能问题

时间:2018-01-13 13:45:22

标签: mysql sql performance

这是我的查询及其性能(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_idsales_status已在tblSales中编入索引。那么你对改进内部查询(计算成功率)有什么建议吗?这样可以提高性能?我一直在玩数学超过2小时。但无法获得更好的方式。

顺便说一下,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等与我的问题无关。

4 个答案:

答案 0 :(得分:1)

许多问题', none 涉及" math"。

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, ...

它还摆脱了 bug ,其中你假设将为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 BYLIMIT通常应放在一起。在您的情况下,您也可以将LIMIT添加到&#34;派生表&#34;,从而导致只有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),但让我们去&#34;覆盖&#34; (首先是sales_time):

INDEX(sales_time, sales_status, order_id)

如果tbloffersPRIMARY KEY(offer_id),则无法再添加其他索引。否则,添加此覆盖索引(按此顺序):

INDEX(offer_id, offer_name)

(向其他回答者道歉;我偷了你的一些想法。)

答案 1 :(得分: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示例

中找到此查询的示例

答案 2 :(得分:0)

  

这里,tblOffers列出了所有的OFFERS。并且tblSales包含所有销售。根据成功率(即那些成功的销售额),我们试图找出最畅销的优惠。

使用简单的JOINGROUP 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。我猜你定义了正确的外键关系,但事实并非如此。

答案 3 :(得分: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