Как создать случайный UUID версии 4 в Oracle?
Этот блог объясняет, что выход sys_guid()
не случайно для каждой системы:
http://feuerthoughts.blogspot.de/2006/02/watch-out-for-sequential-oracle-guids.html
К сожалению, я должен использовать такую систему.
Как обеспечить получение случайного UUID? Это возможно с sys_guid()
? Если нет, то как надежно получить случайный UUID в Oracle?
10 ответов
Я использую это сейчас в качестве обходного пути:
создать или заменить функцию random_uuid вернуть RAW v_uuid RAW(16); начать 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;
Функция требует dbms_crypto
а также utl_raw
, Оба требуют выполнения гранта.
grant execute on sys.dbms_crypto to uuid_user;
Вот полный пример, основанный на ответе @Pablo Santa Cruz и опубликованном вами коде.
Я не уверен, почему вы получили сообщение об ошибке. Это, вероятно, проблема с разработчиком SQL. Все работает нормально, когда вы запускаете его в SQL*Plus и добавляете функцию:
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
Но я бы придерживался SYS_GUID
если возможно. Посмотрите на ID 1371805.1 в моей поддержке Oracle - эта ошибка предположительно исправлена в 11.2.0.3.
РЕДАКТИРОВАТЬ
Какой из них быстрее, зависит от того, как используются функции.
Похоже, что версия Java немного быстрее при использовании в SQL. Однако, если вы собираетесь использовать эту функцию в контексте PL/SQL, функция PL/SQL примерно в два раза быстрее. (Возможно, потому что это позволяет избежать накладных расходов при переключении между двигателями.)
Вот быстрый пример:
--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;
/
GUID версии 4 не является полностью случайным. Некоторые байты должны быть исправлены. Я не уверен, почему это было сделано, или если это важно, но в соответствии с https://www.cryptosys.net/pki/uuid-rfc4122.html:
Процедура генерации UUID версии 4 выглядит следующим образом:
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 соответствуют стандарту.
/questions/43140342/sootvetstvuet-li-oracle-sysguid-uuid-rfc-4122/43140351#43140351
Следующая функция использует sys_guid() и преобразует ее в формат uuid:
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;
Не нужно создавать пакет dbms_crypto и предоставлять его.
Самый простой и короткий способ получить функцию на основе Java для меня:
create or replace function random_uuid return varchar2 as
language java
name 'java.util.UUID.randomUUID() return String';
Я не могу полностью понять, почему это не компилируется, если я добавляю .toString()
хоть.
Меня не полностью удовлетворил ни один из приведенных выше ответов: Java часто не устанавливается, требуется грант и
sys_guid()
является последовательным.
Я остановился на этом.
create or replace function random_uuid return VARCHAR2 is
random_hex varchar2(32);
begin
random_hex := translate(DBMS_RANDOM.string('l', 32), 'ghijklmnopqrstuvwxyz', '0123456789abcdef0123');
return substr(random_hex, 1, 8)
|| '-' || substr(random_hex, 9, 4)
|| '-' || substr(random_hex, 13, 4)
|| '-' || substr(random_hex, 17, 4)
|| '-' || substr(random_hex, 21, 12);
end random_uuid;
/
является (по умолчанию) общедоступным, поэтому гранты не требуются, и он является достаточно случайным. Обратите внимание, что
dbms_random
не является криптографически безопасным, поэтому, если вам это нужно, используйте
dbms_crypto
подход выше. Распределение шестнадцатеричных значений также искажено функцией перевода. Если вам нужен реальный вывод UUID 4, вы можете настроить substr(мне просто нужна уникальность).
Тот же метод можно использовать в sql без функции с некоторым воображением:
select
substr(rand, 1, 8)
|| '-' || substr(rand, 9, 4)
|| '-' || substr(rand, 13, 4)
|| '-' || substr(rand, 17, 4)
|| '-' || substr(rand, 21, 12)
from (select translate(DBMS_RANDOM.string('l', 32), 'ghijklmnopqrstuvwxyz', '0123456789abcdef0123') rand from dual);
Согласно UUID версии 4 формат должен быть xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. Ответ @lonecat предоставляет этот формат, а ответ @ceving частично обеспечивает требования версии 4. Отсутствующая часть имеет формат y, y должен быть одним из 8, 9, a или b.
После смешивания этих ответов и исправления части y код выглядит следующим образом:
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;
Вы можете написать процедуру Java, скомпилировать ее и запустить в Oracle. В этой процедуре вы можете использовать:
UUID uuid = UUID.randomUUID();
return uuid.toString();
Чтобы создать желаемое значение.
Вот ссылка на то, как скомпилировать Java-процедуры в Oracle.
Может быть не уникальным, но генерировать случайную строку типа GUID:
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;
Адаптировано из источника DBMS_RANDOM.STRING.
Принятый ответ от ceving несовместим с RFC4122: два старших значащих бита (биты 6 и 7) clock_seq_hi_and_reserved должны быть установлены в ноль и один, соответственно. Это делает y равным 8,9,a или b в уже упомянутом формате uğur-yeşilyurt xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
Мое решение было сделано прямо в 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;
РЕДАКТИРОВАТЬ:
Хотя первая реализация легко понять, это довольно неэффективно. Следующее решение в 3-4 раза быстрее.
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;
Этот тест показывает, что random_uuid занимает около одной миллисекунды, а random_uuid2 - всего 250 микросекунд. Объединение в первой версии занимало слишком много времени;
declare
dummy_uuid raw(16);
begin
for i in 1 .. 20000 loop
--dummy_uuid := random_uuid;
dummy_uuid := random_uuid2;
end loop;
end;
Есть несколько чистых функций plsql, написанных мной и одним из моих друзей, которые генерируют uuid версии 4 и форматируют любые типы GUID. также форматеры написаны двояко. одна объединяющая строка и одно регулярное выражение для форматирования uuid
CREATE OR REPLACE FUNCTION RANDOM_UUD_RAW
RETURN RAW IS V_UUID RAW(16);
BEGIN V_UUID := SYS.DBMS_CRYPTO.Randombytes(16);
V_UUID := UTL_RAW.Overlay(UTL_RAW.Bit_or(UTL_RAW.Bit_and(UTL_RAW.Substr(V_UUID, 7, 1), '0F'), '40'), V_UUID, 7, 1);
V_UUID := UTL_RAW.Overlay(UTL_RAW.Bit_or(UTL_RAW.Bit_and(UTL_RAW.Substr(V_UUID, 9, 1), '3F'), '80'), V_UUID, 9, 1);
RETURN V_UUID;
END RANDOM_UUD_RAW; --
CREATE OR REPLACE FUNCTION UUID_FORMATTER_CONCAT(V_UUID RAW)
RETURN VARCHAR2 IS V_STR VARCHAR2(36);
BEGIN V_STR := lower(SUBSTR(V_UUID, 1, 8) || '-' || SUBSTR(V_UUID, 9, 4) || '-' || SUBSTR(V_UUID, 13, 4) || '-' || SUBSTR(V_UUID, 17, 4) || '-' || SUBSTR(V_UUID, 21));
RETURN V_STR;
END UUID_FORMATTER_CONCAT; --
CREATE OR REPLACE FUNCTION UUID_FORMATTER_REGEX(V_UUID RAW)
RETURN VARCHAR2 IS V_STR VARCHAR2(36);
BEGIN V_STR := lower(regexp_replace(V_UUID, '(.{8})(.{4})(.{4})(.{4})(.{12})', '\1-\2-\3-\4-\5'));
RETURN V_STR;
END UUID_FORMATTER_REGEX; --
CREATE OR REPLACE FUNCTION RANDOM_UUID_STR
RETURN VARCHAR2 AS BEGIN RETURN UUID_FORMATTER_CONCAT(RANDOM_UUD_RAW());
END RANDOM_UUID_STR; --
CREATE OR REPLACE FUNCTION RANDOM_UUID_STR_REGEX
RETURN VARCHAR2 AS BEGIN RETURN UUID_FORMATTER_REGEX(RANDOM_UUD_RAW());
END RANDOM_UUID_STR_REGEX;