【问题标题】:MySQL subquery optimisationMySQL子查询优化
【发布时间】:2017-01-13 11:02:24
【问题描述】:

我在这里有 4 个最小视图的表格:

销售:

id
has_discount
discount_is_percentage
discount_amount
**sale_date_time**
**order_status**

销售项目:

id
**sales_id**
has_discount
discount_is_percentage
discount_amount
**product_id** (This can sometimes be null)
price_inc_vat_per_item
quantity
vat_rate
is_removed

Sales_payments:

id
**sales_id**
payment_amount
payment_change
payment_method

产品:

id
product_name

我有一个查询,它可以即时计算折扣并报告它。这在记录总数保持在 100-200k 以下的情​​况下效果很好。但是随着数量的增加,所花费的时间真的很慢。我想这是因为我使用的子查询。任何人都可以阐明这一点。每张表上都有一个client_id和outlet_id,用来区别于系统中的其他用户。

目前这些表有 1-3 百万行,而有问题的客户端有 300k-600k。查询需要 30 多秒。对于其他行数较少的人,甚至可以在亚秒内获得它。带星号的是指数。如何改进查询以获得相同的期望结果?我现在的查询:

SELECT  DATE_FORMAT(CONVERT_TZ(sales.sale_date_time,'UTC','Europe/London'),
                '%l%p') as title, count(*) as total_sales, SUM(sales_items.quantity
                   ) as total_quantities,
        SUM(sales_items.price_before_line_discount) as price_before_line_discount,
        SUM(sales_items.price_before_line_discount-sales_items.line_discount) as price_after_line_discount,
        SUM(sales_items.vat_rated_sales) as vat_rated_sales_before_discount,
        SUM(sales_items.zero_rated_sales) as zero_rated_sales_before_discount,
        SUM(sales_items.total_vat_only) as total_vat_only_before_discount,
        SUM(sales_payments.payment_taken) as payment_taken, SUM(sales_items.line_discount) as total_line_discount,
        SUM(sales_payments.payment_cash) as payment_cash, SUM( CASE WHEN sales.has_discount=1
              AND  sales.discount_is_percentage=0 THEN sales.discount_amount WHEN sales.has_discount=1
              AND  sales.discount_is_percentage=1 THEN ((sales_items.price_before_line_discount-sales_items.line_discount)*sales.discount_amount/100) WHEN sales.has_discount=0 THEN 0 END 
           )as total_sales_discount,
        SUM( CASE WHEN sales.has_discount=1 THEN CASE WHEN discount_is_percentage=0 THEN (sales_items.vat_rated_sales*sales.discount_amount)/(sales_items.price_before_line_discount-sales_items.line_discount) WHEN discount_is_percentage=1 THEN (sales_items.vat_rated_sales*((sales_items.price_before_line_discount-sales_items.line_discount)*sales.discount_amount/100))/(sales_items.price_before_line_discount-sales_items.line_discount) END ELSE 0 END )as vat_rated_sales_discount,
        SUM( CASE WHEN sales.has_discount=1 THEN CASE WHEN discount_is_percentage=0 THEN (sales_items.zero_rated_sales*sales.discount_amount)/(sales_items.price_before_line_discount-sales_items.line_discount) WHEN discount_is_percentage=1 THEN ((sales_items.zero_rated_sales*((sales_items.price_before_line_discount-sales_items.line_discount)*sales.discount_amount/100))/(sales_items.price_before_line_discount-sales_items.line_discount)) END ELSE 0 END )as zero_rated_sales_discount,
        SUM( CASE WHEN sales.has_discount=1 THEN CASE WHEN discount_is_percentage=0 THEN (sales_items.total_vat_only*sales.discount_amount)/(sales_items.price_before_line_discount-sales_items.line_discount) WHEN discount_is_percentage=1 THEN (sales_items.total_vat_only*((sales_items.price_before_line_discount-sales_items.line_discount)*sales.discount_amount/100))/(sales_items.price_before_line_discount-sales_items.line_discount) END ELSE 0 END )as total_vat_only_discount
    FROM  `sales`
    left join  
    (
        SELECT  sales_id, SUM(quantity) as quantity, SUM(price_inc_vat_per_item*quantity) AS price_before_line_discount,
                SUM( CASE WHEN has_discount=1
                      AND  discount_is_percentage=0 THEN discount_amount WHEN has_discount=1
                      AND  discount_is_percentage=1 THEN ((price_inc_vat_per_item*quantity)*discount_amount/100) WHEN has_discount=0 THEN 0 END 
                   )as line_discount,
                SUM( CASE WHEN vat_rate>0 THEN CASE WHEN has_discount=1
                      AND  discount_is_percentage=0 THEN ((price_inc_vat_per_item*quantity)-discount_amount) WHEN has_discount=1
                      AND  discount_is_percentage=1 THEN ((price_inc_vat_per_item*quantity)-((price_inc_vat_per_item*quantity)*discount_amount/100)) WHEN has_discount=0 THEN (price_inc_vat_per_item*quantity) END ELSE 0 END 
                   )as vat_rated_sales,
                SUM( CASE WHEN vat_rate=0 THEN CASE WHEN has_discount=1
                      AND  discount_is_percentage=0 THEN ((price_inc_vat_per_item*quantity)-discount_amount) WHEN has_discount=1
                      AND  discount_is_percentage=1 THEN ((price_inc_vat_per_item*quantity)-((price_inc_vat_per_item*quantity)*discount_amount/100)) WHEN has_discount=0 THEN (price_inc_vat_per_item*quantity) END ELSE 0 END 
                   )as zero_rated_sales,
                SUM( CASE WHEN vat_rate>0 THEN CASE WHEN has_discount=1
                      AND  discount_is_percentage=0 THEN ((price_inc_vat_per_item*quantity)-discount_amount)-((price_inc_vat_per_item*quantity)-discount_amount)/(1+(vat_rate/100)) WHEN has_discount=1
                      AND  discount_is_percentage=1 THEN ((price_inc_vat_per_item*quantity)-((price_inc_vat_per_item*quantity)*discount_amount/100))-((price_inc_vat_per_item*quantity)-((price_inc_vat_per_item*quantity)*discount_amount/100))/(1+(vat_rate/100)) WHEN has_discount=0 THEN (price_inc_vat_per_item*quantity)-(price_inc_vat_per_item*quantity)/(1+(vat_rate/100)) END ELSE 0 END 
                   )as total_vat_only
            FROM  sales_items
            WHERE  client_id='0fe26d93-775f-440c-a119-13cbcb6cbc0c'
              AND  is_removed=0
            GROUP BY  sales_id 
    ) as sales_items  ON `sales`.`id` = `sales_items`.`sales_id`
    left join  
    (
        SELECT  sales_id, SUM(payment_amount-payment_change) payment_taken,
                SUM(CASE WHEN payment_method='CASH' THEN (payment_amount-payment_change) ELSE 0 END) as payment_cash
            FROM  sales_payments
            WHERE  client_id='0fe26d93-775f-440c-a119-1396c36cbc0c'
            GROUP BY  sales_id
    ) as sales_payments  ON `sales`.`id` = `sales_payments`.`sales_id`
    WHERE  `sales`.`client_id` = '0fe26d93-775f-440c-a119-1396c36cbc0c'
      and  `sales`.`outlet_id` = 'd5b74bdf-5cef-4455-bf99-13cbcb6cbc0c'
      and  `sales`.`order_status` = 'COMPLETED'
      and  `sale_date_time` >= '2016-01-28 00:00:00'
      and  `sale_date_time` <= '2016-11-28 23:59:00'
    GROUP BY  HOUR(CONVERT_TZ(sales.sale_date_time,'UTC','Europe/London'))
    ORDER BY  `sale_date_time` ASC

更新:

回答@rick-james 的问题

  • 我需要按日期时间字段 sale_date_time 对其进行排序。 Group by 需要按小时报告。它还具有天数、月-年等,具体取决于查询的时间段。
  • 由于设计原因,不得不使用 UUID。整个数据库大约 8GB,这四个表占了大部分。索引长度大于实际数据大小,因为我有很多外键约束。

它位于具有 15GB RAM 的 Amazon Aurora 上。

销售表: 0.5GB 数据 1.3GB 索引

销售项目: 1.3GB 数据 3.2GB 索引

销售付款: 0.5GB 数据 1.1GB 索引

所有表排序规则都是 utf8_unicode_ci。

  • 它使用的是 Aurora 5.6,即 MySQL 5.6。这是解释选择。

ID select_type tables type possible_keys keys key_len ref rows filters extra

1 主要销售参考 sales_client_id_outlet_id_foreign,sales_client_id_index,sales_outlet_id_index,sales_sale_date_time_index,sales_order_status_index sales_client_id_index 108 const 5352 使用 指标条件;使用哪里;使用临时的;使用文件排序

1 PRIMARY ref 108 MyDB.sales.id 10

1 PRIMARY ref 108 MyDB.sales.id 10

3 DERIVED sales_payments ref sales_payments_client_id_outlet_id_foreign,sales_payments_client_id_index sales_payments_client_id_outlet_id_foreign 108 const 5092 使用索引条件;使用哪里;使用临时的;使用文件排序

2 DERIVED sales_items ref sales_items_client_id_outlet_id_foreign,sales_items_client_id_index sales_items_client_id_outlet_id_foreign 108 const 13340 使用 指标条件;使用哪里;使用临时的;使用文件排序

2 衍生产品 eq_ref PRIMARY,products_id_unique PRIMARY 108 MyDB.sales_items.product_id 1

  • 可能会查看将结果存储在 DB 中并从那里获取。唯一的问题是旧订单可以修改,如果发生这种情况,则需要重建总数。

还有其他方法可以重写查询以获得所需的结果吗?

【问题讨论】:

    标签: php mysql subquery query-optimization


    【解决方案1】:
    • ORDER BYGROUP BY 不必要地不同时,需要额外的排序通道。
    • 当数据大于可以缓存在 RAM 中时,UUID 的效率非常低。桌子有多大? `innodb_buffer_pool_size 的值是多少?你有多少内存?
    • LEFT JOIN ( SELECT ... ) 至少在 5.6 之前效率极低。请提供EXPLAIN SELECT ... 看是否优化。你用的是什么版本?
    • 更糟糕的是LEFT JOIN ( SELECT ... ) LEFT JOIN ( SELECT ... )补充:因为我没有看到“自动键”,这很糟糕。这让我怀疑它是否真的是 MySQL 5.6。
    • 构建和维护“汇总表”可能是最终的答案。它可能有一个 PRIMARY KEY,包括 client_id、outlet_id、order_status 和 sale_HOUR。
    • 任何一个子查询本身是否运行缓慢?如果是这样,请开始一个单独的问题以仅关注子查询。请提供来自SHOW CREATE TABLE 的输出;您对表格的描述中缺少很多细节——索引、数据类型、大小、排序规则等。补充:仍然需要这个;还有一些事情需要检查。一个可能的解决方案:CREATE TEMPORARY TABLE 与两个LEFT JOIN SELECTs 中的每一个;然后使用它们。

    【讨论】:

    • 对不起,它在派生表上有 。所以只是从粘贴的代码中删除了它。我将尝试使用临时表方法,看看是否有所改善。
    猜你喜欢
    • 2011-11-27
    • 2021-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多