Как создать UUID версии 4 (random) в Oracle?

этот блог объясняет, что выход sys_guid() не является случайным для каждой системы:

http://feuerthoughts.blogspot.de/2006/02/watch-out-for-sequential-oracle-guids.html

к сожалению, я должен использовать такую систему.

Как обеспечить получение случайного UUID? Возможно ли это с sys_guid()? Если нет, то как надежно получить случайный UUID на Oracle?

8 ответов


вот полный пример, основанный на ответе @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:

процедуры для генерации версия 4 UUID следующие:

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, по-видимому, соответствуют стандарту.


https://stackoverflow.com/a/10899320/1194307

следующая функция использует 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})', '----') into v_uuid from dual;
  return v_uuid;
end random_uuid;

Не нужно создавать пакет dbms_crypto и предоставлять его.


Я использую это сейчас как обходной путь:

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;

функция требует dbms_crypto и utl_raw. Оба требуют выполнения гранта.

grant execute on sys.dbms_crypto to uuid_user;

вы можете написать процедуру 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.СТРОКА.


самый простой и короткий способ получить функцию на основе Java для меня был:

create or replace function random_uuid return varchar2 as
language java
name 'java.util.UUID.randomUUID() return String';

Я не могу полностью понять, почему он не компилируется, если я добавить .toString() хотя.


согласно 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})', '----');

  return v_uuid;
end fn_uuid;

принятый ответ от 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;

EDIT:

хотя первая реализация легко понять, что это скорее неэффективный. Следующее решение в 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;