dbms_utility.get_time可以翻转吗?

时间:2009-07-06 10:29:08

标签: oracle math plsql ora-01426

我遇到了一个庞大的传统PL / SQL程序问题,该程序具有以下逻辑:

l_elapsed := dbms_utility.get_time - l_timestamp;

其中l_elapsedl_timestamp的类型为PLS_INTEGERl_timestamp包含之前调用get_time的结果

在使用ORA-01426: numeric overflow

的批量运行期间,此行突然失败

关于get_time的文档有点模糊,可能是故意的,但它强烈暗示返回值没有绝对意义,并且几乎可以是任何数值。所以我怀疑它被分配给PLS_INTEGER,它只能支持32位整数。然而,互联网上充满了人们正在做这种事情的例子。

当我手动调用get_time时,会发现冒烟枪,它返回的值 -214512572 ,这可疑地接近32位有符号整数的最小值。我想知道在第一次调用get_time和下一次调用之间的时间内,Oracle的内部计数器是否从其最大值和最小值翻转过来,导致在尝试从另一个中减去一个时溢出。

这可能是一个解释吗?如果是这样,这是get_time函数中的固有缺陷吗?我可以等一下,看看今晚批次是否再次失败,但我很想在此之前得到这种行为的解释。

3 个答案:

答案 0 :(得分:3)

也许迟到了,但这可能会让搜索同一问题的人受益。

底层实现是一个简单的32位二进制计数器,从数据库上次启动时开始,每100秒递增一次。

这个二进制计数器被映射到PL / SQL BINARY_INTEGER类型 - 这是一个带符号的32位整数(没有迹象表明它在64位机器上被更改为64位)。

因此,假设时钟从零开始,它将在大约248天后达到+ ve整数限制,然后翻转成为-ve值,回落到零。

好消息是,如果两个数字都是相同的符号,你可以做一个简单的减法来查找持续时间 - 否则你可以使用32位余数。

    IF SIGN(:now) = SIGN(:then) THEN
        RETURN :now - :then;
    ELSE
        RETURN MOD(:now - :then + POWER(2,32),POWER(2,32));
    END IF;

编辑:如果时间间隔过大(248天),此代码将超出int限制并失败,但您不应使用GET_TIME来比较持续时间以天为单位测量(见下文)。

最后 - 问题是你为什么要使用GET_TIME。

从历史上看,它是获得亚秒级时间的唯一方法,但自从引入SYSTIMESTAMP以来,您使用GET_TIME的唯一原因是因为它很快 - 它是32位计数器的简单映射,没有真正的类型转换,并且不会对底层OS时钟功能造成任何影响(SYSTIMESTAMP似乎)。

因为它只测量相对时间,所以它仅用于测量两点之间的持续时间。对于任何需要花费大量时间的任务(你知道,超过1/1000秒左右),使用时间戳的代价是微不足道的。

它实际有用的次数是最小的(我发现的唯一一个是检查缓存中数据的年龄,每次访问的时钟命中都变得很重要。)

答案 1 :(得分:2)

来自10g doc

  

根据平台和机器,数字在-2147483648到2147483647范围内返回,并且您的应用程序在确定间隔时必须考虑该数字的符号。例如,在两个负数的情况下,应用逻辑必须允许第一个(较早的)数字大于接近零的第二个(较晚的)数字。出于同样的原因,您的应用程序还应允许第一个(较早的)数字为负数,第二个(较晚)数字为正数。

因此,虽然将dbms_utility.get_time的结果分配给PLS_INTEGER是安全的,但理论上可以(但不太可能)在执行批处理运行期间发生溢出。这两个值之间的差异将大于2 ^ 31。

如果您的工作需要花费大量时间(因此增加了溢出的可能性),您可能希望切换到TIMESTAMP数据类型。

答案 2 :(得分:0)

为PLS_INTEGER变量指定负值会引发ORA-01426:

SQL> l
  1  declare
  2    a pls_integer;
  3  begin
  4    a := -power(2,33);
  5* end;
SQL> /
declare
*
FOUT in regel 1:
.ORA-01426: numeric overflow
ORA-06512: at line 4

然而,你似乎暗示-214512572接近-2 ^ 31,但事实并非如此,除非你忘记打字。我们正在看吸烟枪吗?

此致 罗布。