我有一个查询,当在数千条记录上执行它时,它运行得非常慢。
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_date
是否在2014-09-20 09:00:00
和2014-09-20 11:00:00
之间重叠。上面的查询涵盖了所有可能重叠的情况。然而,DATE_ADD
增加了很多开销。无论如何要优化
DATE_ADD
?删除DATE_ADD大大提高了性能,但它不会覆盖所有重叠的情况。
最佳答案
我建议您消除OR
。MySQL不会(不能)在meeting_date
的索引上执行范围扫描操作,如果该列包装在函数中(如果比较不是在裸列上执行,而是与必须对每行求值的表达式的结果进行比较)
对于一个大表,显然是一个前导列meeting_date
的索引。
我认为获得更好性能的“诀窍”是重写查询以引入一些额外的领域知识。具体来说,meeting_length
的最小值和最大值是多少?
我想可以肯定的是不会是负面的。我们可能不希望它是零。但即使最小长度大于零,我们也可以使用零作为“已知”最小值。(结果会比其他非零值更方便。)
我们真正需要知道的是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
表单将持续时间添加到datetime。)所以,就像我们要寻找的原始查询一样。
我建议我们包含另一个可搜索谓词。如果已知满足长度的最小值为0,则添加此谓词并不会真正更改对重叠的检查。它所做的是添加一个固定的下限,我们可以检查它。
解释一下。。。如果满足条件“会议结束在时段开始之后”的会议行,则我们还知道,对于该行,“会议开始在(时段开始减去会议长度)之后”。我们还知道“会议开始时间是在(时段开始时间减去会议长度的最大可能值之后)。
对于大多数行,这将是一个更大的范围。。。但是“技巧”是一个谓词,它检查是否可以将“裸”列与常量进行比较。
这意味着MySQL将能够使用索引范围扫描操作来满足这一要求。查询的格式为:
WHERE meeting_date > const
AND meeting_date < const
这是索引范围扫描的完美选择。这应该有利于表现。。。假设有一个合适的索引,并且它显著地限制了需要检查的行数。
但就其本身而言,它返回的行比我们需要的多,我们将得到一些在周期开始之前开始和结束的会议。
所以我们仍然需要额外的检查,以进一步筛选行。但不必对每一行都进行计算,只需要对通过前两个谓词的行进行计算。
AND meeting_date + length > const
我们只需要MySQL认识到
length
不会是负的;认识到这实际上是一个“更严格”的范围,而不是更广泛的范围。它可能可以与AND
一起工作,但是我们可以强制MySQL稍后计算该条件,方法是将其包含在HAVING
子句中。HAVING meeting_date + length > const
但是,所有这些都只是猜测而已。
我们真的需要看看解释输出。
如果前面列为meeting_date的索引还包括id和name列,那么MySQL可以完全从索引中满足查询,而不需要引用基础表中的页面。(如果发生这种情况,我们将在EXPLAIN输出中看到“Using index”。)
早些时候,我说如果我们有一个已知常数,最大值
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'
关于mysql - MySQL DATE_ADD在动态间隔下运行太慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26110970/