可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
This blog explains, that the output of sys_guid()
is not random for every system:
http://feuerthoughts.blogspot.de/2006/02/watch-out-for-sequential-oracle-guids.html
Unfortunately I have to use such a system.
How to ensure to get a random UUID? Is it possible with sys_guid()
? If not how to reliably get a random UUID on Oracle?
回答1:
Here's a complete example, based on @Pablo Santa Cruz's answer and the code you posted.
I'm not sure why you got an error message. It's probably an issue with SQL Developer. Everything works fine when you run it in SQL*Plus, and add a function:
create or replace and compile
java source named "RandomUUID"
as
public class RandomUUID
{
public static String create()
{
return java.util.UUID.randomUUID().toString();
}
}
/
Java created.
CREATE OR REPLACE FUNCTION RandomUUID
RETURN VARCHAR2
AS LANGUAGE JAVA
NAME 'RandomUUID.create() return java.lang.String';
/
Function created.
select randomUUID() from dual;
RANDOMUUID()
--------------------------------------------------------------
4d3c8bdd-5379-4aeb-bc56-fcb01eb7cc33
But I would stick with SYS_GUID
if possible. Look at ID 1371805.1 on My Oracle Support - this bug is supposedly fixed in 11.2.0.3.
EDIT
Which one is faster depends on how the functions are used.
It looks like the Java version is slightly faster when used in SQL. However, if you're going to use this function in a PL/SQL context, the PL/SQL function is about
twice as fast. (Probably because it avoids overhead of switching between engines.)
Here's a quick example:
--Create simple table
create table test1(a number);
insert into test1 select level from dual connect by level <= 100000;
commit;
--SQL Context: Java function is slightly faster
--
--PL/SQL: 2.979, 2.979, 2.964 seconds
--Java: 2.48, 2.465, 2.481 seconds
select count(*)
from test1
--where to_char(a) > random_uuid() --PL/SQL
where to_char(a) > RandomUUID() --Java
;
--PL/SQL Context: PL/SQL function is about twice as fast
--
--PL/SQL: 0.234, 0.218, 0.234
--Java: 0.52, 0.515, 0.53
declare
v_test1 raw(30);
v_test2 varchar2(36);
begin
for i in 1 .. 10000 loop
--v_test1 := random_uuid; --PL/SQL
v_test2 := RandomUUID; --Java
end loop;
end;
/
Version 4 GUIDs are not completely random. Some of the bytes are supposed to be fixed. I'm not sure why this was done, or if it matters, but according to https://www.cryptosys.net/pki/uuid-rfc4122.html:
The procedure to generate a version 4 UUID is as follows:
Generate 16 random bytes (=128 bits)
Adjust certain bits according to RFC 4122 section 4.4 as follows:
set the four most significant bits of the 7th byte to 0100'B, so the high nibble is "4"
set the two most significant bits of the 9th byte to 10'B, so the high nibble will be one of "8", "9", "A", or "B".
Encode the adjusted bytes as 32 hexadecimal digits
Add four hyphen "-" characters to obtain blocks of 8, 4, 4, 4 and 12 hex digits
Output the resulting 36-character string "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
The values from the Java version appear to conform to the standard.
回答2:
https://stackoverflow.com/a/10899320/1194307
The following function use sys_guid() and transform it into uuid format:
create or replace function random_uuid return VARCHAR2 is
v_uuid VARCHAR2(40);
begin
select regexp_replace(rawtohex(sys_guid()), '([A-F0-9]{8})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{12})', '\1-\2-\3-\4-\5') into v_uuid from dual;
return v_uuid;
end random_uuid;
It do not need create dbms_crypto package and grant it.
回答3:
I use this now as a workaround:
create or replace function random_uuid return RAW is
v_uuid RAW(16);
begin
v_uuid := sys.dbms_crypto.randombytes(16);
return (utl_raw.overlay(utl_raw.bit_or(utl_raw.bit_and(utl_raw.substr(v_uuid, 7, 1), '0F'), '40'), v_uuid, 7));
end random_uuid;
The function requires dbms_crypto
and utl_raw
. Both require an execute grant.
grant execute on sys.dbms_crypto to uuid_user;
回答4:
You can write a Java procedure and compile it and run it inside Oracle. In that procedure, you can use:
UUID uuid = UUID.randomUUID();
return uuid.toString();
To generate desired value.
Here's a link on how to compile java procedures in Oracle.
回答5:
It may not be unique, but generate a "GUID-like" random string:
FUNCTION RANDOM_GUID
RETURN VARCHAR2 IS
RNG NUMBER;
N BINARY_INTEGER;
CCS VARCHAR2 (128);
XSTR VARCHAR2 (4000) := NULL;
BEGIN
CCS := '0123456789' || 'ABCDEF';
RNG := 15;
FOR I IN 1 .. 32 LOOP
N := TRUNC (RNG * DBMS_RANDOM.VALUE) + 1;
XSTR := XSTR || SUBSTR (CCS, N, 1);
END LOOP;
RETURN XSTR;
END RANDOM_GUID;
Adapted from source of DBMS_RANDOM.STRING.
回答6:
The easiest and shortest way to get a Java-based function for me was:
create or replace function random_uuid return varchar2 as
language java
name 'java.util.UUID.randomUUID() return String';
I can't completely understand why it does not compile if I add .toString()
though.
回答7:
According to UUID Version 4 format should be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. @lonecat answer provide this format, also @ceving answer partially provide version 4 requirements. Missing part is format y, y should be one of 8, 9, a, or b.
After mixing these answers and fix the y part, code looks like below:
create or replace function fn_uuid return varchar2 is
/* UUID Version 4 must be formatted as xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx where x is any hexadecimal character (lower case only) and y is one of 8, 9, a, or b.*/
v_uuid_raw raw(16);
v_uuid varchar2(36);
v_y varchar2(1);
begin
v_uuid_raw := sys.dbms_crypto.randombytes(16);
v_uuid_raw := utl_raw.overlay(utl_raw.bit_or(utl_raw.bit_and(utl_raw.substr(v_uuid_raw, 7, 1), '0F'), '40'), v_uuid_raw, 7);
v_y := case round(dbms_random.value(1, 4))
when 1 then
'8'
when 2 then
'9'
when 3 then
'a'
when 4 then
'b'
end;
v_uuid_raw := utl_raw.overlay(utl_raw.bit_or(utl_raw.bit_and(utl_raw.substr(v_uuid_raw, 9, 1), '0F'), v_y || '0'), v_uuid_raw, 9);
v_uuid := regexp_replace(lower(v_uuid_raw), '([a-f0-9]{8})([a-f0-9]{4})([a-f0-9]{4})([a-f0-9]{4})([a-f0-9]{12})', '\1-\2-\3-\4-\5');
return v_uuid;
end fn_uuid;
回答8:
Accepted answer from ceving is inconsistent with RFC4122: the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved should be set to zero and one, respectively. That makes y equal to 8,9,a or b in already mentioned by uğur-yeşilyurt format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
My solution made point blank along RFC:
create or replace function random_uuid return raw is
/*
Set the four most significant bits (bits 12 through 15) of the
time_hi_and_version field to the 4-bit version number from
Section 4.1.3.
*/
v_time_hi_and_version raw(2) := utl_raw.bit_and(utl_raw.bit_or(dbms_crypto.randombytes(2), '4000'), '4FFF');
/*
Set the two most significant bits (bits 6 and 7) of the
clock_seq_hi_and_reserved to zero and one, respectively.
*/
v_clock_seq_hi_and_reserved raw(1) := utl_raw.bit_and(utl_raw.bit_or(dbms_crypto.randombytes(1), '80'), 'BF');
/*
Set all the other bits to randomly (or pseudo-randomly) chosen
values.
*/
v_time raw(6) := dbms_crypto.randombytes(6);
v_clock_seq_low_and_node raw(7) := dbms_crypto.randombytes(7);
begin
return v_time || v_time_hi_and_version || v_clock_seq_hi_and_reserved || v_clock_seq_low_and_node;
end random_uuid;
EDIT:
Although first implementation easy to understand it's rather inefficient. Next solution is 3 to 4 times faster.
create or replace function random_uuid2 return raw is
v_uuid raw(16) := dbms_crypto.randombytes(16);
begin
v_uuid := utl_raw.bit_or(v_uuid, '00000000000040008000000000000000');
v_uuid := utl_raw.bit_and(v_uuid, 'FFFFFFFFFFFF4FFFBFFFFFFFFFFFFFFF');
return v_uuid;
end;
This test demostrates that random_uuid takes about one millisecond and random_uuid2 only 250 microseconds. Concatenation in the first version consumed too much time;
declare
dummy_uuid raw(16);
begin
for i in 1 .. 20000 loop
--dummy_uuid := random_uuid;
dummy_uuid := random_uuid2;
end loop;
end;