簡體   English   中英

MySQL子查詢優化

[英]MySQL subquery optimisation

我在這里的最小視圖中有 4 個表:

銷售量:

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

Sales_items:

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

銷售_付款:

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 對其進行排序。 按小時報告需要分組依據。 它還有天、月-年等,取決於查詢的時間段。
  • 由於設計原因,不得不使用 UUID。 整個 DB 大約為 8GB,這四個表擁有大部分。 索引長度大於實際數據大小,因為我有很多外鍵約束。

它在 Amazon Aurora 上運行,內存為 15GB。

銷售表:0.5GB 數據 1.3GB 索引

銷售項目:1.3GB 數據 3.2GB 索引

銷售付款:0.5GB 數據 1.1GB 索引

所有表的整理都是 utf8_unicode_ci。

  • 它使用的是 Aurora 5.6,即 MySQL 5.6。 這是解釋選擇。

ID select_type 表類型 possible_keys 鍵 key_len ref 行過濾額外

1 PRIMARY sales ref 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 主要參考 108 MyDB.sales.id 10

1 主要參考 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 中並從那里獲取。 唯一的問題是可以修改舊訂單,如果發生這種情況,則需要重建總數。

任何其他方式來重寫查詢以獲得所需的結果?

  • ORDER BYGROUP BY ORDER 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輸出; 您對表的描述中缺少很多細節——索引、數據類型、大小、排序規則等。補充:仍然需要這個; 還有一些事情需要檢查。 一個可能的解決方案:使用兩個LEFT JOIN SELECTs每一個CREATE TEMPORARY TABLE 然后使用它們。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM