【问题标题】:Optimize SQL query containing operations and subqueries优化包含操作和子查询的 SQL 查询
【发布时间】:2016-04-07 06:53:49
【问题描述】:

我正在尝试加快 SQL 查询的速度,该查询返回由少数零售商销售的具有给定特征(宽度、高度、直径和负载)的 15 种最便宜的产品。 de DB 中有大约 30 万种产品。总价计算如下:

totalPrice = 数量*(价格 - 折扣*价格)+ 运费

地点:

  • 折扣(百分比)取决于订购的数量、零售商和时间。由于同一组合可能存在许多折扣,因此选择最有利的百分比。
  • shippingCost 取决于订购的数量、一些产品特性以及产品必须交付的目的地

我的问题是 discountshippingsCost 依赖于太多参数来存储每个总价格组合,以使查询运行得更快。因此,我认为我被子查询困住了。


这是一个简化版的 SQL 查询,其中产品数量设置为 2。

SELECT `P`.*, `B`.`name_local` as brandName, `R`.`name` as retailerName, `D`.`amount` as discount,     `S`.`shippingCost`, ROUND(P.price * 2 + IFNULL(S.shippingCost, 0) - IFNULL(P.price * D.amount / 100 * 2, 0), 2 ) as totalPrice
FROM (`Product` P)
JOIN `Brand` B ON `B`.`id` = `P`.`idBrand`
JOIN `Retailer` R ON `R`.`id` = `P`.`idRetailer`
LEFT JOIN `Shipping` S ON `S`.`idRetailer` = `P`.`idRetailer` AND S.nbProduct = (SELECT nbProduct FROM `Shipping` WHERE nbProduct <= 2 ORDER BY nbProduct DESC LIMIT 1)
LEFT JOIN `Discount` D ON `D`.`idRetailer` = `P`.`idRetailer` AND D.amount = (SELECT MAX(amount) FROM Discount D WHERE (D.vehicle = P.vehicle OR D.vehicle = 0) AND D.idRetailer = P.idRetailer AND D.start <= 1451825895 AND D.end >=1451825895)
WHERE `width` =  '195'
AND `height` =  '65'
AND `diameter` =  '15'
AND `load` >= 0
ORDER BY `totalPrice` ASC
LIMIT 15  

我使用的是 mysql 14.14。在我的机器上执行查询大约需要 150 毫秒。通过避免使用当前时间时间戳进行折扣,可以更好地利用 mysql 查询缓存。但是,第一次执行查询需要相当长的时间,并且会很快从查询缓存中刷新(由于许多组合)。以下是explain 命令对查询的结果:

+----+--------------------+----------+--------+-----------------------------------------------+------------+---------+------------------------+-------+----------------------------------------------+
| id | select_type        | table    | type   | possible_keys                                 | key        | key_len | ref                    | rows  | Extra                                        |
+----+--------------------+----------+--------+-----------------------------------------------+------------+---------+------------------------+-------+----------------------------------------------+
|  1 | PRIMARY            | P        | ref    | idBrand,idRetailer,width,height,diameter,load | width      | 12      | const,const,const      | 13268 | Using where; Using temporary; Using filesort |
|  1 | PRIMARY            | S        | ref    | idRetailer                                    | idRetailer | 4       | mydb.P.idRetailer      |     1 | Using where                                  |
|  1 | PRIMARY            | B        | eq_ref | PRIMARY                                       | PRIMARY    | 4       | mydb.P.idBrand         |     1 |                                              |
|  1 | PRIMARY            | R        | eq_ref | PRIMARY                                       | PRIMARY    | 4       | mydb.P.idRetailer      |     1 |                                              |
|  1 | PRIMARY            | D        | ref    | idRetailer                                    | idRetailer | 4       | mydb.S.idRetailer      |     1 |                                              |
|  3 | DEPENDENT SUBQUERY | D        | ref    | idRetailer,start                              | idRetailer | 4       | mydb.P.idRetailer      |     1 | Using where                                  |
|  2 | SUBQUERY           | Shipping | ALL    | NULL                                          | NULL       | NULL    | NULL                   |    48 | Using where; Using filesort                  |
+----+--------------------+----------+--------+-----------------------------------------------+------------+---------+------------------------+-------+----------------------------------------------+

有没有一种优雅的方法来加速这种查询,或者我唯一的方法是提高查询缓存的效果(添加 RAM、增加缓存大小等)?

【问题讨论】:

  • 150 毫秒相当快。
  • 我的其他查询每个不到 2 毫秒。查询在许多不同的网页上进行评估,每个查询都会大大增加页面的第一个字节的时间。此外,处理时间随着存储在数据库中的产品数量的增加而增加(恐怕将来会达到半秒)。我正在尝试找到一种至少低于 50 毫秒的方法。
  • 来自 Web 应用程序的输入是什么?宽度,高度,直径,负载?前三个似乎是产品属性,最后一个呢?
  • 宽度、高度、直径、负载、数量来自 Web 应用程序。宽度、高度、直径和负载是 Product 表中的列。
  • 如何将子查询移动到 FROM 部分以让它们只执行一次。至少运输子查询。貌似不依赖P

标签: mysql sql caching subquery


【解决方案1】:

对于第二个(更复杂的子查询),尝试使用计算出的折扣 LEFT JOIN 让它只执行一次。像这样

...
LEFT JOIN (SELECT MAX(amount) as amount, D.vehicle, D.idRetailer 
FROM Discount D 
WHERE D.start <= 1451825895 
   AND D.end >=1451825895
GROUP BY D.idRetailer, D.vehicle) D ON D.`idRetailer` = `P`.`idRetailer` 
                                 AND (D.vehicle = P.vehicle OR D.vehicle = 0)

【讨论】:

    猜你喜欢
    • 2023-01-03
    • 1970-01-01
    • 1970-01-01
    • 2014-08-10
    • 2011-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多