1

适合所有 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

4 回答 4

2

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

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

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

于 2012-10-16T10:43:56.440 回答
2
  1. 使用解释指示如何优化查询。我建议从 Total 和 TimeStamp 的索引开始

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

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

例如。

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 
于 2012-10-16T10:51:21.317 回答
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您一起成功获得一月份的所有日期和时间。但这需要对表中的每一行DATE()调用该函数(类似于 RBAR 的含义)。RDBMS 无法看穿功能,只知道如何避免浪费时间。相反,我们需要通过重新安排数学来进行优化,从而不需要我们正在过滤的字段上的函数。orders

    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。)

然后,你有你的加入。这是由您已经拥有的索引优化的(products.order_id)。对于上面的片段挑选出的每条记录,优化器可以进行索引搜索并非常快速地识别匹配的记录。


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

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

您可以检查解释计划以查看是否正在使用索引。如果没有,您的帮助尝试将被忽略。可能是因为数据的统计表明不同的加入顺序更好。如果是这样,您可以提供索引来帮助该连接顺序。

于 2012-10-16T11:22:28.133 回答
1

选择 *:

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

!= 运算符是非标准的:

改为使用 <> 运算符来测试不等式。

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

于 2012-10-16T10:49:44.353 回答