【问题标题】:mysql date query always doing full scanmysql日期查询总是做全扫描
【发布时间】:2015-01-22 03:18:57
【问题描述】:

我正在尝试做一个据说非常简单的查询。我有一个带有时间戳的日期时间列的表。

我需要找到所有没有最后 5 分钟时间戳的父表行。如下所述,这可能会逐行更改。我阅读了许多文章,尝试大量更改我的查询,但我的查询仍然没有正确使用索引。

1) 下面显示的访问表可能有不止一行 mon.id。 2) 我需要找到所有在访问表中没有行的 mon.id,在最后一个 mon.duration 分钟内具有 lastaccess_date 日期时间。 3) 访问表可能多于1行,因此需要检查时间戳最新的行的时长逻辑。

表格如下:

mon (parent)
-----------
id,payload,duration

access (child)
---
id,mon_id,lastaccess_date

当前查询是

select id,payload,elapsed,duration from 
(SELECT mon.id,payload,TIMESTAMPDIFF(MINUTE, lastaccess_date, NOW()) as elapsed,duration
    FROM mon
    inner JOIN access_log log on mon.id=log.monitor_id
order by lastaccess_date desc
 ) as t1
GROUP BY id
having elapsed>duration

我还进行了一些其他查询,但这些查询似乎效率不高。如果我有 100 行,那么这些查询没有使用索引并进行全表扫描。

请建议一个可以使用索引的高效查询。如果需要,如果对这种情况有帮助,我可以稍微调整一下表格设计。

这个查询的mysql解释如下:

编辑:根据评论以及我之前已经尝试过的内容,我什至将查询更改为激烈:

select monitor_id
  from access_log
 WHERE access_dt not between date_sub(now(),INTERVAL 5 MINUTE) and now()

现在我没有触及 where 子句中的 access_dt DATETIME 列,但它仍在进行全表扫描。在此测试场景中,查询返回 100 行中的 40 行。

现在是解释:

id, select_type, table, type, possible_keys, key, key_len, ref, rows, filtered, Extra
'1', 'SIMPLE', 'access_log', 'ALL', 'access_dt', NULL, NULL, NULL, '100', '100.00', 'Using where'

【问题讨论】:

  • 您在 DATETIME 列上使用函数会破坏索引的使用。重铸查询以消除该功能将允许利用索引。

标签: mysql sql performance indexing


【解决方案1】:

您的第二个查询的EXPLAIN 不是您所期望的有多种可能性。

首先,不要浪费时间担心小表的 EXPLAIN 结果。现在这是一个很小的表,您的查询返回了其中一半以上。 MySQL 查询计划器可能没有选择索引,只是因为它似乎没有足够的选择性,不值得麻烦分页到 RAM 中并使用。如果是这种情况,情况可能会随着您的表的增长而改变。

其次,你有这个子句:

WHERE access_dt not between date_sub(now(),INTERVAL 5 MINUTE) 
                        and now()

not 可能被证明是无用的,因为它的执行就像它是一样的

WHERE (    access_dt < date_sub(now(),INTERVAL 5 MINUTE)
        OR access_dt > now() )

OR 子句对 MySQL 来说并不有趣。如果你碰巧知道access_dt 的值不可能是将来的,你可以这样做。

WHERE access_dt < date_sub(now(), INTERVAL 5 MINUTE)

这符合索引范围扫描的条件。

第三,您似乎在第一个查询中误用了GROUP BY。你的意思是ORDER BY?很难弄清楚你需要什么。阅读:http://dev.mysql.com/doc/refman/5.6/en/group-by-handling.html

最后,让我们看一下您在第一个查询中的内部查询,并尝试对其进行优化。你从这个开始,我已经编辑它以显示每列来自的表。

SELECT mon.id, mon.payload,
      TIMESTAMPDIFF(MINUTE, log.lastaccess_date, NOW()) as elapsed,
      mon.duration
 FROM mon
inner JOIN access_log log ON mon.id=log.monitor_id
order by log lastaccess_date desc

让我们通过将时间戳选择标准添加到您的 ON 子句来调整它。

  ...
  FROM mon
 INNER JOIN access_log LOG 
       ON mon.id = log.monitor_id
     AND log.lastaccess_date < DATE_SUB(NOW(),INTERVAL mon.duration MINUTE)

这将选择您想要的行。当您获得相对较大的表(access_log 中至少有 10K 行)时,您应该尝试使用以下两个复合索引,看看其中一个或另一个能给您带来更好的结果。

 (monitor_id, lastaccess_date)
 (lastaccess_date, monitor_id)

【讨论】:

  • 感谢@OllieJones 对我的查询的深刻见解!我会生成一些测试数据并尝试您的建议并在这里报告。我尝试使用 GROUP BY 的原因是,子表有多个带有时间戳的行作为父 ID。我试图检查最新的日期时间唯一父 ID,然后对其进行日期检查。但现在看来我可能需要按照你的建议这样做
猜你喜欢
  • 1970-01-01
  • 2015-01-07
  • 2017-10-22
  • 2010-10-12
  • 1970-01-01
  • 2020-05-12
  • 1970-01-01
  • 2021-08-29
  • 1970-01-01
相关资源
最近更新 更多