MySQL 加入日期列存在 1 个月的滞后和性能问题

Posted

技术标签:

【中文标题】MySQL 加入日期列存在 1 个月的滞后和性能问题【英文标题】:MySQL join date columns with 1-month lag and performance issues 【发布时间】:2014-03-31 09:48:00 【问题描述】:

注意:我找到了this similar question,但它没有解决我的问题,所以我不认为这是重复的。

我有两个简单的 mysql 表(使用 MyISAM 引擎创建)Table1Table2

这两个表都有 3 列,一个日期类型列、一个整数 ID 列和一个浮点值列。这两个表都有大约 300 万条记录,非常简单。

表格的内容如下所示(以DateId 作为主键):

Date        Id   Var1
2012-1-27    1    0.1
2012-1-27    2    0.5
2012-2-28    1    0.6
2012-2-28    2    0.7

(假设第二个表的Var1 变为Var2)。

请注意,对于每个(年、月、ID)三元组,只有一个条目。但实际出现的月份中的某一天不一定是最后一天,也不是最后一个工作日,也不是最后一个工作日,等等……只是一个月中的某一天。这一天在其他表格中作为观察日很重要,但在 Table1Table2 之间,月份本身并不重要。

因此,我不能依赖 Date + INTERVAL 1 MONTH 来生成匹配的月份日期,因为它应该匹配提前一个月的日期。

我希望加入 DateId 上的两个表,但第二个表 (Var2) 的值比 Var1 提前 1 个月。

这种代码可以完成它,但我注意到这会显着降低性能,如下所述。

-- This is exceptionally slow for me
SELECT b.Date, 
       b.Id, 
       a.Var1, 
       b.Var2
FROM Table1 a
JOIN Table2 b
ON a.Id = b.Id
AND YEAR(a.Date + INTERVAL 1 MONTH) = YEAR(b.Date)
AND MONTH(a.Date + INTERVAL 1 MONTH) = MONTH(b.Date)


-- This returns quickly, but if I use it as a sub-query
-- then the parent query is very slow.
SELECT Date + INTERVAL 1 MONTH as FutureDate,
       Id,
       Var1
FROM Table1


-- That is, the above is fast, but this is super slow:
select b.Date, 
       b.Id, 
       a.Var1, 
       b.Var2
FROM (SELECT Date + INTERVAL 1 MONTH as FutureDate
             Id,
             Var1
      FROM Table1) a
JOIN Table2 b
ON YEAR(a.FutureDate) = YEAR(b.Date)
AND MONTH(a.FutureDate) = MONTH(b.Date)
AND a.Id = b.Id

我尝试重新排序 JOIN 条件,认为在代码中首先匹配 Id 可能会改变查询执行计划,但似乎没有任何区别。

当我说“超级慢”时,我的意思是上面代码中的选项 #1 不会返回所有 300 万条记录的结果,即使我等待了一个多小时。选项 #2 在不到 10 分钟内返回,但选项 3 再次需要超过 1 小时。

我不明白为什么引入日期延迟会花费这么长时间。

我该怎么做

    分析查询以了解为什么需要很长时间? 根据 1 个月的日期滞后(其中 1 个月的滞后导致的日期可能会导致不匹配),编写更好的查询来连接表。

【问题讨论】:

如何创建和索引dateYeardateMonth 列(作为整数)?我相信影响你表现的是YEARMONTH函数的使用。 这可以工作,但理想情况下我不想到处传播它。实际上有数百个表,例如Table1Table2,需要对其进行该过程。我想我可以尝试一个子查询,它为FutureDate(在我的第二个示例中)返回一个等于100*Year + Month 的列,并查看该子查询在父查询中是否更快,因为连接将只在整数列上. 您找到解决方案了吗?我在使用 YEAR() 和 MONTH() 条件时遇到了类似的缓慢 JOINS 问题。 【参考方案1】:

这是另一种方法:

SELECT b.Date, b.Id, b.Var2
       (select a.var1
        from Table1 a 
        where a.id = b.id and a.date < b.date
        order by a.date
        limit 1
       ) as var1
       b.Var2
FROM Table2 b;

确保首先使用id 设置主索引,然后在Table1 上设置date。否则,创建另一个索引Table1(id, date)

请注意,这假定之前的日期上个月的。

【讨论】:

需要Id 成为主索引的第一部分,以便对每个Id 值(和排序)的重复子查询足够快? @EMS 。 . .是的,这就是原因。您希望相关子查询将索引用于大部分工作。【参考方案2】:

这是另一种解决方法:

SELECT thismonth.Date,
       thismonth.Id, 
       thismonth.Var1 AS Var1_thismonth,
       lastmonth.Var1 AS Var1_lastmonth
  FROM Table2 AS thismonth
  JOIN 
      (SELECT id, Var1, 
              DATE(DATE_FORMAT(Date,'%Y-%m-01')) as MonthStart
         FROM Table2
      ) AS lastmonth
    ON (     thismonth.id = lastmonth.id
         AND thismonth.Date >= lastmonth.MonthStart + INTERVAL 1 MONTH
         AND thismonth.Date <  lastmonth.MonthStart + INTERVAL 2 MONTH
        )

为了让它发挥理想的效果,我认为您需要在 (id, Date, Var1) 上建立一个复合覆盖索引。

它的工作原理是生成一个包含Id,MonthStart,Var1 的派生表,然后通过一系列范围扫描将原始表连接到它。因此,复合覆盖指数。

【讨论】:

lastmonth.MonthStart + INTERVAL 1 MONTH 不会大于thismonth.DateMonthStart 只是将其移回同一个月的 01 日。添加一个 1 个月的 INTERVAL 应该会得到下个月的 01 天,这仍然大于 thismonth.Date,不是吗? 嗯,lastmonth.MonthStart 是上个月的第一天。要将其与日期在本月初或之后的行匹配,请使用thismonth.Date &gt;= lastmonth.MonthStart + INTERVAL 1 MONTH。类似地,要将其匹配到日期在下个月开始之前的行是使用thismonth.Date &lt; lastmonth.MonthStart + INTERVAL 2 MONTH。我仔细检查了这个,我很确定它是正确的。 顺便说一下,如果您需要前一个月没有观察到的月份的结果集行,请用 LEFT JOIN 代替 JOIN。对于这些情况,您将获得 NULL 值。 @GordonLinoff 的回答也是如此。 我明白了,现在不等式条件对我来说是有意义的。谢谢你的澄清。 对。日期不等式是一个臭名昭著的陷阱。为了便于阅读,我通常会定义一个名为TRUNC_MONTH() 的存储函数来处理DATE(DATE_FORMAT(Date,'%Y-%m-01')) 操作。【参考方案3】:

其他答案提供了非常有用的提示,但最终,如果不对我的数据的索引结构进行重大修改(目前这是不可行的),这些方法不会比我的方法更快(在任何有意义的意义上)已经在问题中尝试过。

Ollie Jones 给了我使用日期格式的想法,并且将它与 TIMESTAMPDIFF 函数结合起来似乎使它相当快,尽管我仍然欢迎任何 cmets 解释为什么使用 YEARMONTH、@ 987654324@ 和 TIMESTAMPDIFF 具有如此截然不同的性能属性。

SELECT b.Date, 
       b.Id,
       b.Var2,
       a.Date,
       a.Id,
       a.Var1
FROM Table1 a 
JOIN Table2 b 
ON a.Id = b.Id
AND (TIMESTAMPDIFF(MONTH, 
                   DATE_FORMAT(a.Date, '%Y-%m-01'), 
                   DATE_FORMAT(b.Date, '%Y-%m-01')) = 1)

【讨论】:

以上是关于MySQL 加入日期列存在 1 个月的滞后和性能问题的主要内容,如果未能解决你的问题,请参考以下文章

将 pandas 时间序列切成 n 个月的块

获取从当前日期到下一个完整月的记录(直到下个月的最后一个日期)mysql [重复]

从当前日期回溯 24 个月的 where 子句存在问题

加入 PySpark 数据集中每个月的上个月数据

DB2 如何选择前 2 个月的日期

SQL - 使用窗口函数创建滞后变量