我建议你删除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'