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生成器,它是
1 | SELECT sys_guid(), name FROM my_table2; |
您可以尝试此方法,它可以生成9个guid:
1 | SELECT sys_guid() FROM dual CONNECT BY level < 10; |
当轮子已经存在时,不要试图重新发明它。
事实上,"随机"并不是随机的。如果EDOCX1的种子相同(0),随后调用
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一样,无论从哪一行输出…它将为所有行输出相同的时间戳。
所以,您可以修改您的函数,从每一行中获取一些输入(对于种子?)或者只是使用序列。