关于sql:在批量插入期间缓存oracle函数调用

oracle function call is being cached during bulk insert

当插入大容量行并使用函数调用作为列值之一时,从函数中每10-11行获得完全相同的值。函数实际上正在生成UUID值并返回唯一的结果。如果我用函数的实际代码替换insert语句中的函数调用,它将永远不会重复。

所以我得出的结论是,Oracle实际上缓存了函数的结果,并且只对它插入的每10-11行调用一次。我怎样才能改变这种行为?

我调用的函数来自http://www.oracle-base.com/articles/9i/uuid9i.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CREATE OR REPLACE
FUNCTION        new_uuid RETURN VARCHAR2 AS
  l_seed        BINARY_INTEGER;
  l_random_num  NUMBER(5);
  l_date        VARCHAR2(25);
  l_random      VARCHAR2(4);
  l_ip_address  VARCHAR2(12);
BEGIN
  l_seed := TO_NUMBER(TO_CHAR(SYSDATE,'YYYYDDMMSS'));
  DBMS_RANDOM.initialize (val => l_seed);
  l_random_num := TRUNC(DBMS_RANDOM.value(low => 1, high => 65535));
  DBMS_RANDOM.terminate;

  l_date       := conversion_api.to_hex(TO_NUMBER(TO_CHAR(SYSTIMESTAMP,'FFSSMIHH24DDMMYYYY')));
  l_random     := RPAD(conversion_api.to_hex(l_random_num), 4, '0');
  l_ip_address := conversion_api.to_hex(TO_NUMBER(REPLACE(NVL(SYS_CONTEXT('USERENV','IP_ADDRESS'), '123.123.123.123'), '.', '')));

  RETURN SUBSTR(l_date, 1, 8)                     || '-' ||
         SUBSTR(l_date, 9, 4)                     || '-' ||
         SUBSTR(l_date, 13, 4)                    || '-' ||
         RPAD(SUBSTR(l_date, 17), 4, '0')         || '-' ||
         RPAD(L_RANDOM || L_IP_ADDRESS, 12, '0');
END;

下面是我使用的insert语句:

1
2
3
4
INSERT INTO My_TABLE(ID, NAME,)
SELECT NEW_UUID(), NAME
FROM MY_TABLE2;
COMMIT;

此语句中的select产生大量重复的UUID。虽然此语句产生唯一的语句:

1
2
SELECT RPAD(RPAD(my_schema.conversion_api.to_hex(TRUNC(DBMS_RANDOM.VALUE( 1, 65535))), 4, '0') || my_schema.conversion_api.to_hex(TO_NUMBER(REPLACE(NVL(SYS_CONTEXT('USERENV','IP_ADDRESS'), '123.123.123.123'), '.', ''))), 12, '0') sss
FROM my_schema.MY_TABLE


APC的诊断正确。你需要在随机生成器种子中有熵。

不过,Oracle已经有了一个唯一的ID生成器,它是SYS_GUID()

1
SELECT sys_guid(), name FROM my_table2;

您可以尝试此方法,它可以生成9个guid:

1
SELECT sys_guid() FROM dual CONNECT BY level < 10;

Tora screenshot

当轮子已经存在时,不要试图重新发明它。


事实上,"随机"并不是随机的。如果EDOCX1的种子相同(0),随后调用DBMS_RANDOM.VALUE()将返回相同的结果。过来看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
SQL> EXEC DBMS_RANDOM.initialize (val => 1)

PL/SQL PROCEDURE successfully completed.

SQL> SELECT TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) FROM dual
  2  /

TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
                                       49214

SQL> r
  1* SELECT TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) FROM dual

TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
                                       56385

SQL> r
  1* SELECT TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) FROM dual

TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
                                       23941

SQL> EXEC DBMS_RANDOM.initialize (val => 1)

PL/SQL PROCEDURE successfully completed.

SQL> SELECT TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) FROM dual;

TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
                                       49214

SQL> r
  1* SELECT TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) FROM dual

TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
                                       56385

SQL> r
  1* SELECT TRUNC(DBMS_RANDOM.value(low => 1, high => 65535)) FROM dual

TRUNC(DBMS_RANDOM.VALUE(LOW=>1,HIGH=>65535))
--------------------------------------------
                                       23941

SQL>

如果我们看看你从蒂姆的网站上得到的代码,我们会看到这行:

1
l_seed := TO_NUMBER(TO_CHAR(SYSDATE,'YYYYDDMMSS'));

从中我们可以推测您的进程每秒插入10-11行:)

如果用systimestamp替换sysdate,并将掩码更改为毫秒(或更小),则每次都应获得不同的种子,因此每次都有不同的值。请注意,您仍然需要强制对函数进行重新评估,以确保为每一行获得不同的结果(请参见下面的演示)。

嗯,我说"保证"了吗?哦,哦。正是在随机的性质下,它可以产生相同的结果,两个去运行。所以,也许这应该是"最小化每行获得相同结果的机会"。

或者,从函数中删除初始化,并在开始批量插入之前调用它。这是否可行完全取决于您的业务逻辑。

示范

下面是一个生成"随机"数字的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE OR REPLACE FUNCTION get_random_number
    (p_seed IN NUMBER := 0)
    RETURN pls_integer
IS
BEGIN
    IF p_seed = 0
    THEN
        DBMS_RANDOM.initialize (val => TO_NUMBER(TO_CHAR(SYSDATE,'YYYYDDMMSS')));
    ELSE    
        DBMS_RANDOM.initialize (val => p_seed);
    END IF;
    RETURN TRUNC(DBMS_RANDOM.value(low => 1, high => 65535));
END;
/

如果使用默认参数调用它20次,它每次返回相同的数字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
SQL> SELECT rownum
       , get_random_number
FROM   dual
CONNECT BY level <= 20
/
  2    3    4    5  
    ROWNUM GET_RANDOM_NUMBER
---------- -----------------
         1             10239
         2             10239
         3             10239
         4             10239
         5             10239
         6             10239
         7             10239
         8             10239
         9             10239
        10             10239
        11             10239
        12             10239
        13             10239
        14             10239
        15             10239
        16             10239
        17             10239
        18             10239
        19             10239
        20             10239

20 ROWS selected.

SQL>

然而,如果我们传递一个值,它每次都使用不同的种子,并且Lo!我们得到了不同的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
SQL> SELECT rownum
       , get_random_number(rownum)
FROM   dual
CONNECT BY level <= 20
/
  2    3    4    5  
    ROWNUM GET_RANDOM_NUMBER(ROWNUM)
---------- -------------------------
         1                     49214
         2                      6476
         3                     42426
         4                      2370
         5                     48546
         6                     52483
         7                      6964
         8                     46764
         9                     27569
        10                      7673
        11                     52446
        12                     50229
        13                     27861
        14                     31413
        15                     11518
        16                     13471
        17                     38766
        18                      9949
        19                     61656
        20                     25797

20 ROWS selected.

SQL>

这是因为传入rownum会强制计算每行的函数。您不应该在生产系统中使用rownum作为种子:时间戳更好。或者将日期时间与rownum连接,为每行提供唯一的种子。


还没有尝试过,但我相信Oracle正在计算一次新的_uid()函数的值,并为每个返回的行输出(就像您选择了systimestamp一样,无论从哪一行输出…它将为所有行输出相同的时间戳。

所以,您可以修改您的函数,从每一行中获取一些输入(对于种子?)或者只是使用序列。