【问题标题】:MySQL DATE_ADD running too slow with dynamic intervalMySQL DATE_ADD 在动态间隔下运行太慢
【发布时间】:2014-09-30 00:35:28
【问题描述】:

我有以下查询,在对数千条记录执行时运行速度很慢。

SELECT
    name,
    id
FROM
   meetings
WHERE
  meeting_date < '2014-09-20 11:00:00' AND (
  meeting_date >= '2014-09-20 09:00:00' OR
  DATE_ADD(meeting_date, INTERVAL meeting_length SECOND) > '2014-09-20 09:00:00'
)

查询检查meeting_date2014-09-20 09:00:002014-09-20 11:00:00 之间是否存在重叠。上面的查询涵盖了所有可能的重叠情况。但是,DATE_ADD 增加了很多开销。

无论如何优化DATE_ADD?删除 DATE_ADD 会大大提高性能,但不会涵盖所有重叠的情况。

【问题讨论】:

  • meeting_length的值有上限吗?
  • 它不认为它真的是 DATE_ADD 函数本身;相反,它是表达式必须计算的行数,因为 OR,并且因为表达式上的谓词不是 sargable。
  • @OllieJones,实际上并不是会议时长的上限,但假设不超过 24 小时是安全的。

标签: mysql query-optimization dateadd


【解决方案1】:

我建议你删除OR

当列包含在函数中时,MySQL 不会(不能)对列 meeting_date 上的索引执行范围扫描操作。

当比较是针对裸列时,MySQL 可以进行范围扫描。但是通过与表达式的比较,MySQL 必须为表中的 每一 行计算该表达式,然后进行比较。

对于一个大表,我们会通过具有meeting_date 前导列的索引获得最佳性能。

我认为获得更好性能的“诀窍”是重写查询以引入一些额外的领域知识。具体来说,meeting_length 的 MINIMUM 和 MAXIMUM 值是多少?

我认为假设它不会是负数是很安全的。我们可能不希望它为零。但即使最小长度大于零,我们也可以使用零作为我们的“已知”最小值。 (事实证明它比其他一些非零值更方便。)

我们真正需要知道的是meeting_length 的最大值。如果这是一个已知的常量值,那就太好了,因为我们将在查询中包含该值。假设meeting_length的最大值是7天的秒数。

作为我的想法的演示:

SELECT m.name
     , m.id
  FROM meetings m
 WHERE m.meeting_date  < '2014-09-20 11:00:00' 
   AND m.meeting_date  > '2014-09-20 09:00:00' + INTERVAL -7 DAY
HAVING m.meeting_date  + INTERVAL meeting_length SECOND 
                       > '2014-09-20 09:00:00'

让我们稍微解开一下。

第一个谓词与原始查询中的相同...会议的“开始”时间是指定时间段的“结束”之前。

第三个谓词也与您的查询相同...会议的“结束”是在指定时间段的开始之后。 (我个人的偏好是使用+ INTERVAL 表单为日期时间添加持续时间。)

所以,就像我们正在寻找重叠的原始查询一样。

我建议我们包含另一个 sargable 谓词。添加这个谓词并没有真正改变对重叠的检查,因为我们知道 meeting_length 的最小值为 0。它所做的是添加一个我们可以检查的固定下限。

稍微解释一下...如果满足条件“会议结束在期间开始之后”的会议行,那么我们也知道,对于该行,“会议开始在(期间开始 MINUS 会议之后长度)”。而且我们也知道“会议开始在(期间开始减去会议长度的最大可能值。

对于大多数行,这将是一个更大的范围......但“技巧”是检查可以将“裸”列与常量进行比较的谓词。

这意味着 MySQL 将能够使用索引范围扫描操作来满足这一要求。查询格式为:

 WHERE meeting_date > const 
   AND meeting_date < const

这对于索引范围扫描来说是完美的。这应该有利于性能...假设有一个合适的索引并且显着限制了需要检查的行数。

但就其本身而言,返回的行数超出了我们的需要,我们将获得一些在周期开始之前开始和结束的会议。

所以我们仍然需要额外的检查,以进一步过滤行。但这不必对每一行进行评估,只需对通过前两个谓词的行进行评估。

   AND meeting_date + length > const

我们只需要让 MySQL 认识到 length 永远不会是负数;认识到这实际上是一个“更严格”的范围,而不是更广泛的范围。它可能适用于AND,但我们可以通过将其包含在HAVING 子句中来强制MySQL 稍后评估该条件。

HAVING meeting_date + length > const

但是,这一切都只是猜测。

我们真的需要看看 EXPLAIN 输出。

如果以 meeting_date 为前导列的索引还包括 id 和 name 列,则 MySQL 可以完全从索引满足查询,而无需引用基础表中的页面。 (如果发生这种情况,我们将在 EXPLAIN 输出中看到“使用索引”。)


之前,我说过如果我们有一个已知的最大常数 meeting_length 会很方便。

我们还可以使用查询从数据中确定:

SELECT MAX(meeting_length) FROM meetings

(并且以 meeting_length 作为前导列的索引将避免对表进行昂贵的全扫描)

我们使用该值推导出谓词中的“常量”值。

我们可以包含该查询(作为内联视图或子查询),但这可能会影响性能。 (我们需要测试 MySQL 优化器的“智能”程度......

我们可以尝试将其作为子查询:

SELECT m.name
     , m.id
  FROM meetings m
 WHERE m.meeting_date  < '2014-09-20 11:00:00' 
   AND m.meeting_date  > '2014-09-20 09:00:00' 
                       - INTERVAL (SELECT MAX(l.meeting_length) FROM meetings l) DAY
HAVING m.meeting_date  + INTERVAL meeting_length SECOND 
                       > '2014-09-20 09:00:00'

或者作为内联视图试试:

SELECT m.name
     , m.id
  FROM ( SELECT MAX(l.meeting_length) AS max_seconds
           FROM meetings l
       ) d
 CROSS
  JOIN meetings m
 WHERE m.meeting_date  < '2014-09-20 11:00:00' 
   AND m.meeting_date  > '2014-09-20 09:00:00' 
                       - INTERVAL d.max_seconds SECOND
HAVING m.meeting_date  + INTERVAL meeting_length SECOND 
                       > '2014-09-20 09:00:00'

【讨论】:

  • 感谢您的详细回答。确实,它成功了。我使用子查询来获取最大会议长度。
  • @PeteDarrow:该子查询应该只评估一次,然后比较的右侧将基本上是一个常数。 EXPLAIN 输出应在索引上显示“范围”操作;我不确定这是否会显示“const”,但至少它不应该显示 DEPENDENT SUBQUERY。只要有人不添加具有 HUGE meeting_length 值的行,就应该限制要检查的行数。
  • @PeteDarrow:您是否尝试将HAVING 关键字替换为AND,以测试将第三个谓词移至WHERE 子句时的性能? (这可能会非常糟糕,恢复到以前的性能。或者,它可能会稍微提高性能。) WHERE 子句中的谓词在访问行时进行评估。限制检索的行。在检索到所有行之后,HAVING 子句中的谓词才被评估。)
  • 我删除了HAVING 子句,性能似乎与使用HAVING 相同或更好一些。感谢您的提示。
猜你喜欢
  • 2011-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多