2

我遇到了一个庞大的遗留 PL/SQL 过程的问题,它具有以下逻辑:

l_elapsed := dbms_utility.get_time - l_timestamp;

wherel_elapsedl_timestamp是类型PLS_INTEGERl_timestamp保存先前调用的结果get_time

在批处理运行期间,这条线突然开始失败ORA-01426: numeric overflow

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

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

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

4

3 回答 3

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 位计数器的简单映射,没有真正的类型转换,并且不会对底层操作系统时钟功能产生任何影响(似乎是 SYSTIMESTAMP)。

由于它只测量相对时间,它只用于测量两点之间的持续时间。对于任何需要大量时间的任务(你知道,超过 1/1000 秒左右),使用时间戳的成本是微不足道的。

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

于 2010-06-24T13:51:01.473 回答
2

来自10g 文档

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

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

如果您的工作需要很长时间(因此增加了发生溢出的机会),您可能需要切换到 TIMESTAMP 数据类型。

于 2009-07-06T10:51:17.493 回答
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,但事实并非如此,除非您忘记输入数字。我们在看确凿的证据吗?

问候,罗布。

于 2009-07-06T11:03:04.177 回答