缓慢存储的 MySQL 函数随着重复运行而逐渐变慢

Posted

技术标签:

【中文标题】缓慢存储的 MySQL 函数随着重复运行而逐渐变慢【英文标题】:Slow stored MySQL function gets progressively slower with repeated runs 【发布时间】:2020-07-04 21:40:30 【问题描述】:

我需要根据此人是否有约会来过滤列表。运行时间为 0.09 秒。

select personid from persons p
where EXISTS (SELECT 1 FROM appointments a
                    WHERE a.personid = p.personid); 

由于我在多个查询中使用它,并且它实际上包含另一个条件,所以将过滤器放入函数中似乎很方便,所以我有

CREATE FUNCTION `has_appt`(pid INT) RETURNS tinyint(1)
BEGIN
RETURN 
    EXISTS (SELECT 1 FROM appointments WHERE personid = pid);
END 

那我就可以用了

select personid from persons where has_appt(personid)

然而,发生了两件意想不到的事情。首先,使用 has_appt() 函数的语句现在需要 2.5 秒才能运行。我知道函数调用有开销,但这似乎很极端。其次,如果我重复运行该语句,每次大约需要多 5 秒,所以到第 4 次,它需要超过 20 秒。无论我在两次尝试之间等待多长时间都会发生这种情况,但再次存储该函数会将时间重置为 2.5 秒。什么可以解释渐进式缓慢?简单地运行多次会影响什么状态?

我知道解决方案是忘记该函数并将其嵌入到我的查询中,但我想了解原理,这样我就可以避免再次犯同样的错误。提前感谢您的帮助。

我正在使用 mysql 8 和 Workbench。

【问题讨论】:

【参考方案1】:

您的原始查询可以被替换并加速

SELECT personid FROM appointments;

但是这个查询看起来很愚蠢——为什么你想要一个有约会的人的所有 id 的列表,但没有关于他们的信息?也许您过度简化了查询?

如果一个人可能有多个约会,那么这将是必要的,并且可能不会那么快:

SELECT DISTINCT personid FROM appointments;

至于为什么函数这么慢……优化看不到函数里面是什么。于是select personid from persons where has_appt(personid)遍历整个persons表,反复调用函数。

【讨论】:

是的,我把问题用最简单的术语说清楚了。我的实际实现当然不是简单地提取personids。但是让它变得如此简单可以清楚地表明,当我调用一个函数时,当我在函数之外做同样的事情时,会发生一些非常不同的事情。无论哪种方式,它都会遍历整个 person 表,所以如果我理解正确并且没有解释差异,那就没有什么不同。我错过了什么吗? @MikeBlyth - personid 是否在 appointments 中编入索引?请为这两个表提供SHOW CREATE TABLEEXPLAIN SELECT ... 现在我确实看到使用该函数会强制为每个人单独查找约会,而不是优化器可能执行的优化的一次性匹配。但是为什么每次运行的时间都会增加呢?是的,约会按人员以及人员+日期进行索引。 @MikeBlyth - 可能还有很多其他更微妙的问题。即使我们解决了“简化”查询,同样的技术可能与“真实”查询相关,也可能不相关。 当然,但主要是我对简化的感兴趣,因为我想了解原理。我不是为了学习而寻找一个好的优化原理,我想我已经理解了要点,即函数必须一遍又一遍地进行查找,而在查询中这样做可能会避免那。存在某种状态似乎很奇怪,其中持续时间取决于查询已运行多少次。

以上是关于缓慢存储的 MySQL 函数随着重复运行而逐渐变慢的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法绕过 Python list.append() 随着列表的增长而在循环中逐渐变慢?

同一 SPROC 的每次迭代执行时间变慢

什么可能导致函数导入性能变慢?

mysql加了性别变慢

函数指针是否使程序变慢?

MySQL速度变慢,怎么办