mysql - MySQL DATE_ADD在动态间隔下运行太慢

标签 mysql query-optimization dateadd

我有一个查询,当在数千条记录上执行它时,它运行得非常慢。

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:002014-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/

相关文章:

php - MySQL 使用 PHP sprintf 插入到 NULL INT 列

mysql - 子查询中的Order By对外查询排序的影响

TSQL - 添加缺失的日期

mysql - 使用 date_add 函数和 BQ 的时间间隔翻译 MYSQL 查询的问题

mysql - 如何在 Zend Framework2 中使用 MySql 的方法 DATE_ADD、DATE_FORMAT

php - 如何从现有数据库更新 Symfony 实体?

php - 如何设计大约 50 列的表并选择用户所需的列

MySQL - 带有内连接的 SQL 查询花费太长时间

mysql:查询返回根据特定用户的分数排名

mysql - 索引这个 mysql 查询