【问题标题】:MySQL Query Optimisation - JOIN?MySQL 查询优化 - 加入?
【发布时间】:2012-10-06 10:44:29
【问题描述】:

为所有 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 秒才能完成 - 这太长了。我正在寻找一种优化它的方法,可能使用临时表或不同类型的连接。恐怕我对这两个概念的理解非常有限。

谁能建议我优化此查询的方法?

【问题讨论】:

  • 您的 o.total 值是否小于 0?如果不是更好,则将不等于运算符 (!=) 替换为大于 (>)
  • 不幸的是我这样做了,所以恐怕我不得不坚持使用 != 运算符!

标签: mysql sql join query-optimization


【解决方案1】:

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

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

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

【讨论】:

  • 除非这个特定的答案改变了功能。 timestamp'2012-01-31 23:59:59.99' 是什么?根本不要使用BETWEEN,而是使用... o.timestamp &gt;= '2012-01-01' AND o.timestamp &lt; '2012-02-01'
  • 是的,你说得对,你需要得到两个常量,切断时间,然后在结束日期上加一天,答案更多的是关于这个想法。我换个答案
  • MySQL 中没有DATEADD()。这是+ INTERVAL 1 DAY
  • :) 哎呀抱歉,功能超过列的错误非常频繁,我只是想表明应该改变这一点
【解决方案2】:

选择 *:

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

!= 运算符是非标准的:

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

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

【讨论】:

  • 最佳实践的良好指针,但在这种情况下与性能完全无关。
  • 感谢您的提示。从现在开始编写查询时,我会牢记这一点。
【解决方案3】:
  1. 使用Explain 指示如何优化查询。我建议从 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 

【讨论】:

  • 我认为您应该解释为什么删除 DATE() 调用可能会有所帮助。我不同意BETWEEN '2012-01-01' AND '2012-01-31 23:59''2012-01-31 23:59:59' 呢?还是'2012-01-31 23:59:59.99'?不要在日期时间范围内使用BETWEEN,而是使用o.timestamp &gt;= '2012-01-01' AND o.timestamp &lt; '2012-02-01'
  • +1 我同意@Dems 的观点,即应该谨慎使用 BETWEEN,但甚至可以用于比较日期。但 +1 用于解释,这对于解决性能问题至关重要。
【解决方案4】:

首先,我会使用不同风格的语法。 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 选择的每条记录,优化器都可以进行索引搜索并非常快速地识别出匹配的记录。


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

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

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

【讨论】:

  • 这是一个绝妙的答案 - 感谢您如此彻底地解释!我唯一有点困惑的部分是向时间戳字段添加索引。我一直认为这个字段不是唯一的——因此无法被索引。我的理解有误吗?
  • @dai.hop - 不,字段不需要是非唯一的才能被索引。主键通常是默认索引(作为聚集索引或非聚集索引)。
  • 优秀。我已按照建议应用了索引,现在查询运行时间为 1.9 秒 - 一个显着的改进!
  • @dai.hop - 虽然没有我预期的那么多。您是否也进行了其他更改? (到查询本身?)
  • 是的,我在索引时间戳字段后运行了最终答案。我的索引信息现在看起来像这样screenshot
猜你喜欢
  • 2023-03-03
  • 2022-10-24
  • 1970-01-01
  • 2011-01-22
  • 2011-07-07
  • 2018-12-21
  • 2010-12-15
相关资源
最近更新 更多