【问题标题】:Why is my custom MySQL function so much slower than inlining same in query?为什么我的自定义 MySQL 函数比查询中的内联函数慢得多?
【发布时间】:2011-10-25 10:45:01
【问题描述】:

我反复使用这个SELECT 查询来读取代表 IPv4 地址的无符号整数,并将它们呈现为人类可读的点状四进制字符串。

SELECT CONCAT_WS('.', 
  FLOOR(ip/POW(256,3)),
  MOD(FLOOR(ip/POW(256,2)), 256),
  MOD(FLOOR(ip/256), 256),
  MOD(ip, 256))
FROM ips;

使用我的测试数据,执行此查询需要 3.6 秒

我认为 creating a custom stored function 用于 int->string 转换可以更容易阅读查询并允许重用,所以我做了这个:

CREATE FUNCTION IntToIp(value INT UNSIGNED)
  RETURNS char(15)
  DETERMINISTIC
  RETURN CONCAT_WS(
    '.', 
    FLOOR(value/POW(256,3)),
    MOD(FLOOR(value/POW(256,2)), 256),
    MOD(FLOOR(value/256), 256),
    MOD(value, 256)
  );

使用这个函数,我的查询看起来像这样:

SELECT IntToIp(ip) FROM ips;

但根据我的测试数据,这需要 13.6 秒来执行。

我希望这在第一次运行时会变慢,因为涉及到额外的间接级别,但慢了近 4 倍似乎过分了。 预计会有这么慢吗?

我在 Ubuntu 10.10 上使用开箱即用的 MySQL 服务器 5.1,没有进行任何配置更改。


要重现我的测试,请创建一个表并填充 1,221,201 行:

CREATE TABLE ips (ip INT UNSIGNED NOT NULL);

DELIMITER //
CREATE PROCEDURE AddIps ()
BEGIN
  DECLARE i INT UNSIGNED DEFAULT POW(2,32)-1;
  WHILE (i>0) DO
    INSERT INTO ips (ip) VALUES (i);
    SET i = IF(i<3517,0,i-3517);
  END WHILE;
END//
DELIMITER ;

CALL AddIps();

【问题讨论】:

    标签: mysql performance user-defined-functions


    【解决方案1】:

    不要重新发明轮子,使用INET_NTOA()

    mysql> SELECT INET_NTOA(167773449);
        -> '10.0.5.9'
    

    【讨论】:

    • 哇。 RTFM FTW。 +1 以加快我的具体案例的速度,但接受的是 guido,他回答了通用的“为什么函数比内联慢”问题。
    • 与其他解决方案相比,使用此内置函数仅需 0.7 秒:time mysql -u root test -e "SELECT INET_NTOA(ip) FROM ips" &gt; /dev/null
    【解决方案2】:

    使用这个你可以获得更好的性能:

    CREATE FUNCTION IntToIp2(value INT UNSIGNED)
      RETURNS char(15)
      DETERMINISTIC
      RETURN CONCAT_WS(
        '.', 
        (value >> 24),
        (value >> 16) & 255,
        (value >>  8) & 255,
         value        & 255
      );
    
    > SELECT IntToIp(ip) FROM ips;
    1221202 rows in set (18.52 sec)
    
    > SELECT IntToIp2(ip) FROM ips;
    1221202 rows in set (10.21 sec)
    

    在我的系统(四核(fedora 64 位)上的 2gB mysql 5.1 实例)上添加测试数据后启动原始 SELECT 需要 4.78 秒。

    编辑:预计会这么慢吗?

    是的,存储过程很慢,比解释/编译代码慢很多。当您需要绑定一些您希望将其排除在应用程序之外的数据库逻辑时,它们会变得很有用,因为它在特定域之外(即日志记录/管理任务)。如果存储的函数不包含查询,最好用您选择的语言编写实用函数,因为这不会阻止重用(没有查询),并且运行速度会更快。

    这就是在这种特殊情况下,您应该改用 INET_NTOA 函数的原因,该函数可用并满足您的需求,正如 sanmai 回答中所建议的那样。

    【讨论】:

    • 与相同调整的内联版本相比,这会给您带来更好的性能吗?我(还)没有追求更快的函数,只是想了解为什么函数版本比内联版本慢得多
    • time mysql -u root test -e "SELECT IntToIp2(ip) FROM ips" &gt; /dev/null 为 7.4 秒,time mysql -u root test -e "SELECT CONCAT_WS('.', (ip&gt;&gt;24), (ip&gt;&gt;16)&amp;255, (ip&gt;&gt;8)&amp;255, ip&amp;255) FROM ips" &gt; /dev/null 为 1.3 秒,所以这次函数调用开销是 5 倍以上。
    • @Day 不直接查询要快很多;我假设存储函数本质上比解释/编译代码慢,只是想向您展示一个更好的实现;顺便说一句,当存储的函数/过程不包含查询时,最好实现与外部函数相同。
    • 不客气;如果你真的需要在数据库中实现一些功能,还有用 c/c++ 开发的 UDF 可以提高性能。
    • 当然,对于任何阅读 cmets 到此已接受答案的人,在这种特定情况下,您应该按照 sanmai 的回答中的建议使用内置的 INET_NTOA 函数
    猜你喜欢
    • 2021-10-29
    • 1970-01-01
    • 2021-03-10
    • 2019-08-28
    • 2013-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多