Как получить версию 4 (случайный) UUID?
В этом блоге разъясняется, что значения возвращаемые функцией sys_guid() на самом деле не случайны на некоторых системах.
K несчастью, я работаю на такой системе.
Как получить случайный UUID?
Возможно ли это с sys_guid()? Если нет, то как получить достоверно случайный UUID?
Свободный перевод How to generate a version 4 (random) UUID on Oracle? от участника @ceving
Полезные комментарии:
В общем, UUID не являются действительно случайными. – @jpaugh
Под "случайным UUID" я имел ввиду "тип 4 UUID". Думаю, здесь все понимают, что псевдослучайность значит. – @ceving
Ответы (3 шт):
Вот полностью рабочий пример (основан на ответе @Pablo Santa Cruz):
create or replace and compile java source named "RandomUUID" as
public class RandomUUID {
public static String create() {
return java.util.UUID.randomUUID().toString();
}
}
/
create or replace function RandomUUID return varchar2 as
language java name 'RandomUUID.create() return java.lang.String';
/
SQL> select randomUUID() uuid from dual;
UUID
--------------------------------------------------------------------------------
f83bf110-96e7-45d2-9abc-8ebabde8ee5f
Но лучше испольовать по возможности SYS_GUID.
Посмотрите ID 1371805.1 на MOS (прим. ред.: требует регистрации) - этот баг был исправлен в версии 11.2.0.3.
Дополнение
Какой из вариантов - Java или PL/SQL - производительней, зависит от того, как функция используется.
Прим. ред.: комментирование результатов сравнения производительности опустил, т.к. больше не соответствуют действительности.
Сравним на этом примере:
create table t1 as
select level n from dual connect by level <= 10e5;
declare
t number;
c number;
rawval raw (16);
chrval varchar2 (36);
begin
t := dbms_utility.get_time;
select count(*) into c from t1 where to_char(a) > RandomUUID ();
dbms_output.put_line ('SQL/JAVA elapsed '||(dbms_utility.get_time-t)/100);
t := dbms_utility.get_time;
select count(*) into c from t1 where to_char(a) > random_uuid ();
dbms_output.put_line ('SQL/PLSQL elapsed '||(dbms_utility.get_time-t)/100);
t := dbms_utility.get_time;
for i in 1..10e5 loop chrval := RandomUUID; end loop;
dbms_output.put_line ('PLSQL/JAVA elapsed '||(dbms_utility.get_time-t)/100);
t := dbms_utility.get_time;
for i in 1..10e5 loop rawval := random_uuid; end loop;
dbms_output.put_line ('PLSQL/PLSQL elapsed '||(dbms_utility.get_time-t)/100);
end;
/
SQL/JAVA elapsed ,01
SQL/PLSQL elapsed ,01
PLSQL/JAVA elapsed 50,67
PLSQL/PLSQL elapsed 40,21
В версии 4 значения GUID не полностью случайны. Некоторые байты имеют фиксированые значения. Не уверен, почему так сделали, или имеет ли это вообще значение, но в соответствии с Generate a UUID compliant with RFC 4122:
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"
Значения, которые генерирует Java функция, по всей видимости соответствуют стандарту.
Свободный перевод ответа от участника @Jon Heller
Пока, как временное решение пользуюсь:
create or replace function random_uuid return raw is
uuid raw (16);
begin
uuid := sys.dbms_crypto.randomBytes (16);
return (utl_raw.overlay (utl_raw.bit_or (utl_raw.bit_and (utl_raw.substr(uuid, 7, 1), '0F'), '40'), uuid, 7));
end random_uuid;
/
Функции необходимы dbms_crypto и utl_raw. Обоим нужно дать execute привилегию.
grant execute on sys.dbms_crypto to your_user;
Вообще, я просто в шоке от того, насколько это не унифицировано.
Вот здесь есть список того, как генерировать UUID'ы в разных версиях SQL.
А в ORACLE (w-t-f!) (несмотря на то, что некоторых хелпах написано про функцию RANDOM_UUID()) приходится писать как то так:
INSERT INTO "ITDT1" (
"ID", NAME) values (
regexp_replace(rawtohex(sys_guid()),
'(.{8})(.{4})(.{4})(.{4})(.{12}).*',
'\1-\2-\3-\4-\5'), 'wtf-2');