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 引擎创建)Table1
和 Table2
。
这两个表都有 3 列,一个日期类型列、一个整数 ID 列和一个浮点值列。这两个表都有大约 300 万条记录,非常简单。
表格的内容如下所示(以Date
和Id
作为主键):
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)三元组,只有一个条目。但实际出现的月份中的某一天不一定是最后一天,也不是最后一个工作日,也不是最后一个工作日,等等……只是一个月中的某一天。这一天在其他表格中作为观察日很重要,但在 Table1
和 Table2
之间,月份本身并不重要。
因此,我不能依赖 Date + INTERVAL 1 MONTH
来生成匹配的月份日期,因为它应该匹配提前一个月的日期。
我希望加入 Date
和 Id
上的两个表,但第二个表 (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 个月的滞后导致的日期可能会导致不匹配),编写更好的查询来连接表。
【问题讨论】:
如何创建和索引dateYear
和dateMonth
列(作为整数)?我相信影响你表现的是YEAR
和MONTH
函数的使用。
这可以工作,但理想情况下我不想到处传播它。实际上有数百个表,例如Table1
和Table2
,需要对其进行该过程。我想我可以尝试一个子查询,它为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.Date
? MonthStart
只是将其移回同一个月的 01
日。添加一个 1 个月的 INTERVAL
应该会得到下个月的 01
天,这仍然大于 thismonth.Date
,不是吗?
嗯,lastmonth.MonthStart 是上个月的第一天。要将其与日期在本月初或之后的行匹配,请使用thismonth.Date >= lastmonth.MonthStart + INTERVAL 1 MONTH
。类似地,要将其匹配到日期在下个月开始之前的行是使用thismonth.Date < lastmonth.MonthStart + INTERVAL 2 MONTH
。我仔细检查了这个,我很确定它是正确的。
顺便说一下,如果您需要前一个月没有观察到的月份的结果集行,请用 LEFT JOIN 代替 JOIN。对于这些情况,您将获得 NULL 值。 @GordonLinoff 的回答也是如此。
我明白了,现在不等式条件对我来说是有意义的。谢谢你的澄清。
对。日期不等式是一个臭名昭著的陷阱。为了便于阅读,我通常会定义一个名为TRUNC_MONTH()
的存储函数来处理DATE(DATE_FORMAT(Date,'%Y-%m-01'))
操作。【参考方案3】:
其他答案提供了非常有用的提示,但最终,如果不对我的数据的索引结构进行重大修改(目前这是不可行的),这些方法不会比我的方法更快(在任何有意义的意义上)已经在问题中尝试过。
Ollie Jones 给了我使用日期格式的想法,并且将它与 TIMESTAMPDIFF
函数结合起来似乎使它相当快,尽管我仍然欢迎任何 cmets 解释为什么使用 YEAR
、MONTH
、@ 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 个月的滞后和性能问题的主要内容,如果未能解决你的问题,请参考以下文章