MySQL查询优化 - 加入?

时间:2012-10-16 10:42:31

标签: mysql sql join query-optimization

一个适合所有MySQL专家: - )

我有以下查询:

SELECT o.*, p.name, p.amount, p.quantity 
FROM orders o, products p 
WHERE o.id = p.order_id AND o.total != '0.00' AND DATE(o.timestamp) BETWEEN '2012-01-01' AND '2012-01-31' 
ORDER BY o.timestamp ASC
  • 订单表= 80,900行
  • 产品表= 125,389行
  • o.id和p.order_id已编入索引

查询大约需要6秒钟 - 这太长了。我正在寻找一种优化它的方法,可能使用临时表或不同类型的连接。我担心我对这两个概念的理解非常有限。

有人可以建议我优化此查询吗?

4 个答案:

答案 0 :(得分:2)

我不是MySQL专家(更多SQL Server),我认为你最好在o.timestamp上有索引,你需要像这样重写你的查询

o.timestamp >= '2012-01-01' and o.timestamp <= '2012-01-31' + INTERVAL 1 DAY

逻辑是 - 如果比较列和常量上的某个表达式,则index将不起作用。您需要比较列和常量

答案 1 :(得分:2)

  1. 使用Explain指示如何优化查询。我建议从Total和TimeStamp上的索引开始

  2. 您可能会发现删除date功能可以提高性能。

  3. 您应该使用现代语法。

  4. 例如。

    SELECT o.*, p.name, p.amount, p.quantity  
    FROM orders o
         inner join products p  
         on o.id = p.order_id 
    WHERE o.total != '0.00' 
    AND o.timestamp BETWEEN '2012-01-01' AND '2012-01-31 23:59'  
    ORDER BY o.timestamp ASC 
    

答案 2 :(得分:2)

首先,我会使用不同的语法风格。 ANSI-92已经有20年的时间了,许多RDBMS实际上建议不要使用你使用过的符号。在这种情况下它不会有所作为,但由于一系列原因(我会让你调查并自行决定),这确实是非常好的做法。

最终答案和示例语法:

SELECT
  o.*, p.name, p.amount, p.quantity  
FROM
  orders
INNER JOIN
  products
    ON orders.id = products.order_id 
WHERE
      orders.timestamp >= '2012-01-01'
  AND orders.timestamp <  '2012-02-01'
  AND orders.total     != '0.00' 
ORDER BY
  orders.timestamp ASC

由于orders表是您正在进行初始过滤的表,因此这是开始查看优化的好地方。


使用DATE(o.timestamp) BETWEEN x AND y,您可以在1月份获得所有日期和时间。但这需要调用DATE() 每一行 上的orders函数(类似于RBAR的含义)。 RDBMS无法通过看到只知道如何避免浪费时间的功能。相反,我们需要通过重新安排数学来不需要我们正在过滤的字段上的函数来进行优化。

    orders.timestamp >= '2012-01-01'
AND orders.timestamp <  '2012-02-01'

此版本允许优化器知道您想要一个彼此顺序的日期块。它被称为范围寻求。它可以使用索引快速找到适合该范围的第一条记录和最后一条记录,然后选择其间的每条记录。这样可以避免检查所有不适合的记录,甚至可以避免检查范围中间的所有记录;只需要寻找边界。

假设所有记录都按日期排序,并且优化者可以看到。为此,您需要一个索引。考虑到这一点,您可以使用两个基本覆盖索引:
   - (id, timestamp)
   - (timestamp, id)

首先是我看到人们使用最多的东西。但这会迫使优化者分别对每个timestamp执行id范围搜索。而且由于每个id可能有不同的timestamp值,因此您什么都没得到。

第二个索引是我推荐的。

现在,优化工具可以非常快速地填充查询的这一部分......

SELECT
  o.*
FROM
  orders
WHERE
      orders.timestamp >= '2012-01-01'
  AND orders.timestamp <  '2012-02-01'
ORDER BY
  orders.timestamp ASC

实际上,即使ORDER BY已使用建议的索引进行了优化。它已经按照您希望输出数据的顺序排列。加入后无需重新排序。


然后,要满足total != '0.00'要求,仍会检查范围内的每一行。但是你已经把范围缩小了很多,这可能会很好。 (我不会参与其中,但您可能会发现无法在MySQL中使用索引来优化 timestamp范围搜索。) < / p>

然后,你有了你的加入。这已经通过您已经拥有(products.order_id)的索引进行了优化。对于上面的代码段所挑选的每条记录,优化器都可以进行索引搜索并快速识别匹配的记录。


这一切都假定,在绝大多数情况下,每个订单行都有一个或多个产品行。例如,如果只有非常精选的几个订单有任何产品行,那么首先选择感兴趣的产品行可能会更快;基本上以相反的顺序查看连接。

优化器实际上是为你做出决定的,但是知道它正在这样做是很方便的,然后提供你估计对它最有用的索引。

您可以查看解释计划以查看是否正在使用索引。如果没有,你的帮助尝试被忽略了。可能是因为数据的统计意味着不同的加入顺序更好。如果是这样,那么您可以提供索引来帮助改变连接顺序。

答案 3 :(得分:1)

SELECT *:

如果表的架构发生更改,选择带有*通配符的所有列将导致查询的含义和行为发生更改,并可能导致查询检索过多数据。

!=运算符是非标准的:

使用&lt;&gt;运算符来测试不等式。

没有AS关键字的别名: 在列或表别名中明确使用AS关键字(例如“tbl AS别名”)比隐式别名(如“tbl alias”)更具可读性。