如何提高查询执行时间?

时间:2017-12-18 09:54:09

标签: mysql sql query-optimization rdbms

我有一个相当复杂的查询,需要1-2分钟才能执行。有没有办法改善执行时间?

以下是查询:

select  o.orders_id, o.customers_id, o.customers_name, s.orders_status_name,
        ot.text as order_total, ot.value, DATEDIFF(NOW(), payment_data_read_status) as numDaysLeft,
        ( SELECT  ifnull(sum(op.paid_amount), 0)
            from  orders_payment op
            where  op.orders_id=o.orders_id
              AND  op.confirm_payment='1'
        ) as paid_total
    from  orders o, orders_total ot, orders_status s
    where  o.orders_id = ot.orders_id
      and  ot.class = 'ot_total'
      and  o.orders_status = s.orders_status_id
      and  s.language_id = '1'
      AND  ROUND(ot.value,2) != ROUND(
                ( SELECT  ifnull(sum(op.paid_amount),0)
                    from  orders_payment op
                    where  op.orders_id=o.orders_id
                      AND  op.confirm_payment='1'
                ), 2) 

查询说明

enter image description here

一些细节

  

订单中的记录数= 7321

     

orders_total = 22167

中的记录数      

orders_payment中的记录数量= 12038

     

orders_status = 9中的记录数

orders_id列是订单表中的自动增量。首先我想在订单表中索引orders_id列,但因为它是主要的,所以我认为它不会起作用。

EDITS 错误

enter image description here

3 个答案:

答案 0 :(得分:1)

我发现嵌套查询不一定是坏的,但我尽量避免将它们放在选择列表中。这是我的建议:

select
    o.orders_id,
    o.customers_id,
    o.customers_name,
    s.orders_status_name, 
    ot.text as order_total, 
    ot.value, 
    datediff(now(), payment_data_read_status) as numdaysleft, 
    ifnull(op.paid_total, 0) paid_total
from
    orders o
    join
    orders_total ot
    on o.orders_id = ot.orders_id 
    join
    orders_status s
    on o.orders_status = s.orders_status_id 
    left outer join
    (
        select 
            orders_id,
            sum(ifnull(paid_amount, 0)) as paid_total
        from
            orders_payment 
        where
            confirm_payment = '1'
        group by
            orders_id
    ) op 
    on
        op.orders_id = o.orders_id
where
    ot.class = 'ot_total' and 
    s.language_id = '1' and
    round(ot.value,2) != round(ifnull(op.paid_total, 0), 2);

我认为这会让优化者有更好的机会做好工作。

请注意,我已将"组放在"在" op"的内部查询中。如果没有这个,我想你可能会欺骗优化器为每个结果行运行这个查询而不只是一次。

使用您拥有的卷,您不需要任何索引;他们可能会让事情变得更糟而不是更好,但要测试它,看看会发生什么。

我还没有能够测试我的建议,但如果你提供创建表脚本和一些数据,我会这样做。如果我在查询中输入任何拼写错误,请道歉。

答案 1 :(得分:0)

类似于Ron Ballard的回答,但是在子查询中进行舍入,并切换到显式连接语法: -

SELECT o.orders_id, 
        o.customers_id, 
        o.customers_name, 
        s.orders_status_name, 
        ot.text as order_total, 
        ot.value, 
        DATEDIFF(NOW(), payment_data_read_status) as numDaysLeft, 
        sub0.paid_amount_sum as paid_total 
FROM orders o
INNER JOIN orders_total ot ON o.orders_id = ot.orders_id
INNER JOIN orders_status s ON o.orders_status = s.orders_status_id 
INNER JOIN
(
    SELECT orders_id,
            COALESCE(SUM(op.paid_amount),0) AS paid_amount_sum,
            ROUND(COALESCE(SUM(op.paid_amount),0), 2) AS paid_amount_sum_rounded
    FROM orders_payment op 
    WHERE op.confirm_payment = '1'
    GROUP BY orders_id
) sub0
ON sub0.orders_id = o.orders_id 
WHERE ot.class = 'ot_total' 
AND s.language_id = '1' 
AND ROUND(ot.value,2) != sub0.paid_amount_sum_rounded

答案 2 :(得分:0)

233结果vs 55的问题可能是测试... != ...,其中一个可能是NULL。返回NULL,将其视为false,因此就像=

解决这个问题的一种方法是添加

AND sub0.paid_amount_sum_rounded IS NOT NULL

所需索引:

o:  INDEX(orders_status, orders_id)  -- in this order
ot:  INDEX(class, orders_id)  -- in either order

远离这个和其他答案:

  • 使用JOIN ... ON ...语法。 (这是为了清楚;它对性能没有影响。)
  • 关注可能LEFT JOIN结果的NULL和子查询。
  • 避免WHERE
  • 中的子查询
  • 确保有合适的索引(以避免表扫描导致性能下降)。
  • 了解'复合'索引的好处。