SQL查询中年龄计算的性能

Posted

技术标签:

【中文标题】SQL查询中年龄计算的性能【英文标题】:Performance of age calculation in SQL query 【发布时间】:2013-07-15 00:18:20 【问题描述】:

我正在构建一个能够处理许多不同搜索条件的动态 mysql 用户搜索查询。我考虑编写一个存储过程,但最终在客户端构建查询(php 中的准备语句)。其中一个标准是能够搜索用户的年龄,即 X 和 Y 岁之间。我想知道如何尽可能有效地做到这一点。最终查询将相当复杂,并且有多个连接,并且将来可能会在几百万行上运行,因此我需要尽可能优化它。我将用户的出生日期存储在具有YYYY-MM-DD 格式的索引DATE 列中。我有以下用于计算用户年龄的用户定义函数 (UDF):

RETURN (DATE_FORMAT(current_time, '%Y') - DATE_FORMAT(date_of_birth, '%Y') - (DATE_FORMAT(current_time, '00-%m-%d') < DATE_FORMAT(date_of_birth, '00-%m-%d')));

计算的细节并不重要;我更关心它是如何使用的。我担心的一个问题是,在我的 WHERE 子句中使用这个 UDF 会显着降低查询速度,因为它需要在每一行上运行,即使我使 UDF 具有确定性。我不能保证在检查年龄之前会有其他标准来缩小匹配行的范围。我不能只检查出生日期和日期,因为那是不准确的。我正在考虑是否将上述计算从 UDF 中提取出来并将其直接嵌入到查询的 WHERE 子句中是否会产生明显的差异(我认为是的)。不利的一面是 WHERE 子句会因这样的计算而变得更加复杂(或者实际上是两个,除非有办法重用结果)。但我想没有办法避免这些计算。在 WHERE 子句中执行此计算是否是提高性能的方法,还是有更好的方法?

理论上,我想我什至可以在user 表中添加一个age 列,并计算用户注册并每晚运行预定作业/cronjob 以更新今天生日的用户的年龄(如果我能有效地选择它)。这肯定会加快我的搜索查询,但会引入冗余数据。因此,如果无法在搜索查询本身内有效地完成计算,我真的只想这样做。

所以,总结一下:我需要搜索年龄范围内的用户(例如 25 到 30 岁)。我应该在 WHERE 子句中计算年龄,还是会因为必须在每一行上完成而非常慢?这是我必须做出的牺牲,还是我有更好的选择?

非常感谢任何帮助。

【问题讨论】:

【参考方案1】:

如果您想根据当前日期进行准确的年龄计算,那么您应该尝试以下方法:

where date_of_birth between date(now()) - interval 30 years and date(now()) - interval 25 year

在这种情况下,您没有date_of_birth进行任何转换,因此可以使用索引进行查询。

另外,你不应该使用如下表达式:

DATE_FORMAT(current_time, '%Y') - DATE_FORMAT(date_of_birth, '%Y')

DATE_FORMAT() 将参数转换为字符串。你想要一个数字,所以只需使用:

year(now()) - year(date_of_birth)

它将日期到字符串的转换保存到 int,然后直接转到 int。

编辑:

要处理“25”真正意味着“最多26”的情况,请使用显式比较来实现逻辑:

where date_of_birth >= date(now()) - interval 30 years and
      date_of_birth < date(now()) - interval 26 year

【讨论】:

感谢您的回答。例如,对于年龄,25 岁​​半的人仍应通过搜索 20-25 岁的查询来匹配。所以我不会从今天的日期严格减去 25 年,而是希望匹配那些 25 岁但尚未 26 岁的人。我希望这是有道理的,如果我不清楚,我很抱歉那。你知道如何做到这一点吗?这也是我没有采用区间方法的原因。我使用的功能只是我在某处找到的一个,知道转换不是一个好习惯。我会像你说的那样更新它。 计算他们的出生日期才能满足标准而不是试图计算他们当前的年龄绝对是要走的路。【参考方案2】:

这与 UDF 或存储过程的性能无关。每当您在列周围使用函数时,MySQL 都不能在其上使用索引。

如果您不希望 Highlander 出现在您的数据库中,那么一个用于年龄的 tinyint 无符号列就足够了 (0-255)。这需要 1 个字节/行。你可以在上面放一个索引。此列添加到表中的开销是微不足道的。不要害怕存储空间。另一方面,存储性能是更大的问题。完全扫描搜索的成本远高于这 1 字节的额外列。

您可以使用 date_of_birth 列上的触发器更新此列。当然,如果您在表上放置适当的索引,则每晚 cronjob 可以有效地选择 date_of_birth = DATE(NOW()) 的行并将年龄增加一。 (我会使用存储过程来做到这一点,所以一切都可以在 MySQL 中完成)。

ps.:您编写的函数似乎是一个存储函数而不是 UDF。存储函数用 SQL 编写并存储在 MySQL 中。 UDF 用 C 语言编写,编译为 .so 或 .dll 文件并加载到 MySQL。更多信息您可以查看:Help with SP and UDF?

【讨论】:

谢谢。我完全同意你的看法。但是,我关心的不是额外的字节,而是更多的是避免“维护”年龄。虽然它会相对简单,但 cronjobs、触发器等确实使我的应用程序整体变得更加复杂。如果有必要,这并不是一件坏事(它确实会给我带来很好的性能),但如果在搜索查询中无法有效地进行比较,我会更乐意采用这样的解决方案。否则,为了简单起见,我宁愿处理那里的所有事情。 :-)

以上是关于SQL查询中年龄计算的性能的主要内容,如果未能解决你的问题,请参考以下文章

如何写在身份证号码中提取年龄的sql语句

如何写在身份证号码中提取年龄的sql语句

sql中根据身份证号来计算年龄

SQL 中怎么根据出生日期算出年龄 然后用年龄查询

SQL计算年龄

计算sql查询的性能