我有以下查询在数千条记录上执行时运行速度很慢。
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极大地提高了性能,但它不会涵盖所有重叠的情况。
答案 0 :(得分:2)
我建议您删除OR
。当该列被包装在一个函数中时(当没有在裸列上执行比较,但是比较时),MySQL不会对meeting_date
上的索引执行范围扫描操作(不能)#(t = t; t)到必须为每一行计算的表达式的结果。)
对于大型表,显然是前导列为meeting_date
的索引。
我认为&#34;技巧&#34;获得更好的性能是重写查询以引入一些额外的领域知识。具体来说,meeting_length
的MINIMUM和MAXIMUM值是多少?
我认为假设它不会是消极的,这是非常安全的。我们可能不希望它为零。但即使最小长度大于零,我们也可以使用零作为我们已知的&#34;已知的#34;最小。 (事实证明它比其他非零值更方便。)
我们真正需要知道的是meeting_length
的MAXIMUM值。如果这是一个已知的常量值,那就太好了,因为我们将在查询中包含该值。我们假设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'
让我们解开一点。
第一个谓词与原始查询中的相同...&#34; start&#34;会议时间之前&#34;结束&#34;指定期间。
第三个谓词也与查询中的相同...&#34; end&#34; 在指定期间的开头之后会议。 (我个人的偏好是使用+ INTERVAL
表单为日期时间添加持续时间。)
因此,就像原始查询一样,我们正在寻找重叠。
我建议我们包含另一个可搜索的谓词。鉴于我们对meeting_length已知最小值为0,因此添加此谓词并不会真正改变对重叠的检查。它做的是添加一个我们可以检查的固定下限。
要解释一下......如果满足条件&#34;会议结束的会议行是在句号开始之后&#34;,那么我们也知道,对于那一行,#34;会议开始是在(期间开始MINUS会议长度)&#34;。而且我们也知道&#34;会议开始时间之后(期间开始减去会议长度的最大可能值。
对于大多数行来说,这将是一个更大的范围......但是&#34;技巧&#34;是检查可以比较&#34;裸&#34;的谓词。反对常数的列。
这意味着MySQL将能够使用索引范围扫描操作来满足这一要求。查询的格式为:
WHERE meeting_date > const
AND meeting_date < const
这对索引范围扫描来说非常完美。这应该有利于性能...假设有一个合适的索引,并且显着限制了需要检查的行数。
但就其本身而言,返回的行数超出了我们的需要,我们将会在会议开始之前开始和结束会议。
因此我们仍然需要额外检查,以进一步过滤行。但是不必为每一行评估,只有通过前两个谓词的行。
AND meeting_date + length > const
我们只需要让MySQL认识到length
永远不会消极;认识到这实际上是一个更严格的&#34;范围,而不是更广泛的范围。它可能适用于AND
,但我们可以强制MySQL稍后通过将其包含在HAVING
子句中来评估该条件。
HAVING meeting_date + length > const
但是,所有这些只是猜测。
我们真的需要看一下EXPLAIN输出。
如果带有meeting_date前导列的索引也包含id和name列,则MySQL可以完全从索引中满足查询,而无需引用基础表中的页面。 (如果发生这种情况,我们会在EXPLAIN输出中看到&#34;使用索引&#34;)
早些时候,我说如果我们有一个已知的常数最大meeting_length
会很方便。
我们还可以使用查询从数据中确定:
SELECT MAX(meeting_length) FROM meetings
(使用meeting_length作为前导列的索引将避免对表进行昂贵的全扫描)
我们使用该值来推导&#34;常数&#34;谓词中的值。
我们可以包含该查询(作为内联视图或子查询),但这可能会影响性能。 (我们需要测试&#34;智能&#34; 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'