这是我的查询及其性能(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
`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 等与我的问题无关。
最佳答案
许多“问题”,没有涉及“数学”。
JOIN
让事情变得困难。 LEFT JOIN
说“我不关心该行是否存在于'右'表中。(我怀疑你不需要LEFT
??)但它也说“右表中可能有多行。根据列名称,我猜测每个offer_id
只有一个offer_name
。如果这是正确的,那么这是我的第一个建议。 (这将使优化器相信 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()
。假设您没有“ future ”的 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)
(向其他回答者道歉;我窃取了您的一些想法。)
关于mysql - 使用数学计算进行查询的性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48240474/