首先,我会使用不同风格的语法。 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)
。对于上面的片段挑选出的每条记录,优化器可以进行索引搜索并非常快速地识别匹配的记录。
这一切都假定,在绝大多数情况下,每个订单行都有一个或多个产品行。例如,如果只有极少数订单有任何产品行,那么首先挑选出感兴趣的产品行可能会更快;本质上是查看以相反顺序发生的连接。
优化器实际上会为您做出决定,但知道它正在这样做很方便,然后提供您估计对它最有用的索引。
您可以检查解释计划以查看是否正在使用索引。如果没有,您的帮助尝试将被忽略。可能是因为数据的统计表明不同的加入顺序更好。如果是这样,您可以提供索引来帮助该连接顺序。