首先,我会使用不同风格的语法。 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,您可以成功获取一月份的所有日期和时间。但这需要对orders 表中的每一行调用DATE() 函数(类似于RBAR 的含义)。 RDBMS 不能看穿函数来知道如何避免浪费时间。相反,我们需要通过重新安排数学运算来进行优化,从而不需要我们正在过滤的字段上的函数。
orders.timestamp >= '2012-01-01'
AND orders.timestamp < '2012-02-01'
此版本允许优化器知道您需要一个彼此连续的日期块。这称为范围搜索。它可以使用索引非常快速地找到适合该范围的第一条记录和最后一条记录,然后挑选出其中的每条记录。这样可以避免检查所有不适合的记录,甚至可以避免检查范围中间的所有记录;只需要找出边界。
假设所有记录都按日期排序,并且优化器可以看到这一点。为此,您需要一个索引。考虑到这一点,您似乎可以使用两个基本的覆盖索引:
- (id, timestamp)
- (timestamp, id)
第一个是我看到人们使用最多的东西。但这迫使优化器分别为每个id 执行timestamp range-seek。而且由于每个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 range-seek。)
那么,你就加入了。这是由您已经拥有的索引(products.order_id) 优化的。对于上面的 sn-p 选择的每条记录,优化器都可以进行索引搜索并非常快速地识别出匹配的记录。
这一切都假设在绝大多数情况下,每个订单行都有一个或多个产品行。例如,如果只有极少数订单有任何产品行,那么首先挑选出感兴趣的产品行可能会更快;本质上是查看以相反顺序发生的连接。
优化器实际上会为您做出决定,但知道它正在这样做很方便,然后提供您估计对它最有用的索引。
您可以检查解释计划以查看索引是否正在使用。如果没有,您的帮助尝试将被忽略。可能是因为数据的统计表明不同的加入顺序更好。如果是这样,您可以提供索引来帮助连接顺序。