【问题标题】:How to generate a version 4 (random) UUID on Oracle?如何在 Oracle 上生成版本 4(随机)UUID?
【发布时间】:2022-04-27 20:33:30
【问题描述】:

这篇博文解释说,sys_guid() 的输出对于每个系统都不是随机的:

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

不幸的是,我不得不使用这样的系统。

如何保证得到一个随机的 UUID? sys_guid() 可以吗?如果不是如何在 Oracle 上可靠地获取随机 UUID?

【问题讨论】:

  • 一般来说,UUID 是not reliably random
  • @jpaugh 使用“随机 UUID”我打算使用“类型 4 UUID”。我想这里的每个人都知道pseudorandomness 是什么意思。
  • 没错,但我完全误解了你的要求。如果需要,请查看并调整我的编辑。

标签: oracle uuid


【解决方案1】:

这是一个完整的示例,基于 @Pablo Santa Cruz 的回答和您发布的代码。

我不确定您为什么收到错误消息。这可能是 SQL Developer 的问题。当您在 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。查看 My Oracle Support 上的 ID 1371805.1 - 此错误据说已在 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;
/

版本 4 GUID 不是完全随机的。一些字节应该是固定的。我不确定为什么这样做,或者是否重要,但根据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 版本中的值似乎符合标准。

【讨论】:

  • 如何确定调用 Java 函数是否比我在工作中使用的 Oracles dbms_crypto 包更快?
  • 我的 Solaris 系统上的 UID 是可预测的,应用程序不允许这样做。
  • @Jon Heller 我尝试了 SYS_GUID() 但结果不是我所期望的。 UUID 版本 4 应该看起来像 xxxxxxxxxxxx-4xxx-yxxx-xxxxxxxxxxxx 还是?
  • @ZerOne SYS_GUID() 似乎工作正常,请参阅编辑。
  • @JonHeller 感谢您的编辑。也许我误解了你,但你的意思是它以他们自己的 Oracle 方式工作正确吗?因为它不像根据 RFC 应该具有的格式
【解决方案2】:

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})', '\1-\2-\3-\4-\5') into v_uuid from dual;
  return v_uuid;
end random_uuid;

它不需要创建 dbms_crypto 包并授予它。

【讨论】:

  • 谢谢,但regexp_replace 听起来很贵。 UUID 生成需要非常快。
  • 这个函数不会在所有平台上都是随机的。例如,在 Solaris 上,每次执行时只有一两个字符会发生变化。
【解决方案3】:

我现在使用它作为解决方法:

创建或替换函数 random_uuid 返回 RAW 是
  v_uuid RAW(16);
开始
  v_uuid := sys.dbms_crypto.randombytes(16);
  返回 (utl_raw.overlay(utl_raw.bit_or(utl_raw.bit_and(utl_raw.substr(v_uuid, 7, 1), '0F'), '40'), v_uuid, 7));
结束random_uuid;

该函数需要dbms_cryptoutl_raw。两者都需要执行授权。

grant execute on sys.dbms_crypto to uuid_user;

【讨论】:

  • 您能否解释一下将 utl_raw 函数应用于 randombytes 输出时发生了什么?
  • @tribunal88 我会说一些电子在周围漂浮。 ;-)
  • 我的意思是为什么我们需要对 randombytes(16) 输出应用任何后处理?
  • 从头开始。我明白你在做什么。根据 RFC 4122 的第 4.1.3 节,您将 UUID 的版本号设置为“4”。ietf.org/rfc/rfc4122.txt
  • 从技术上讲,这不符合 RFC 的 v4,因为第 9 字节的两个最高有效位应设置为 10。因此,提取第 7、8 和 9 个字节,将它们与0x0FFFBF 逐位与,然后将它们与0x400080 逐位或,然后再覆盖它们。
【解决方案4】:

对我来说,获得基于 Java 的函数的最简单和最短的方法是:

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

虽然我无法完全理解为什么如果我添加 .toString() 它不会编译。

【讨论】:

    【解决方案5】:

    您可以编写一个 Java 过程并在 Oracle 中对其进行编译和运行。在该过程中,您可以使用:

    UUID uuid = UUID.randomUUID();
    return uuid.toString();
    

    产生期望的价值。

    Here's 一个关于如何在 Oracle 中编译 java 过程的链接。

    【讨论】:

    • 是否有任何可用的示例?
    • 是的,看看我刚刚发布的链接。 Oracle 的数据库在其上下文中有一个 Java 虚拟机,可以在其上下文中运行这种 Java 代码,而无需运行外部程序
    • 不起作用。一旦我尝试使用 UUID,我的 Oracle 就拒绝编译它:pastebin.com/kL4jB2KX
    • 我询问了如何在 Oracle 的 PL/SQL 中使用 java.util.UUID,但到目前为止还没有答案:forums.oracle.com/forums/…
    【解决方案6】:

    它可能不是唯一的,但会生成一个“类似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 的源代码。

    【讨论】:

    • 你可以添加一个像@lonecat这样的regexp_replace来格式化。
    【解决方案7】:

    根据 UUID Version 4 格式应该是 xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx。 @lonecat answer 提供此格式,@ceving answer 部分提供版本 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;
    

    【讨论】:

      【解决方案8】:

      ceving 接受的答案与RFC4122 不一致:clock_seq_hi_and_reserved 的两个最高有效位(位 6 和 7)应分别设置为零和一。这使得 uğur-yeşilyurt 格式 xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx 已经提到的 y 等于 8,9,a 或 b

      我的解决方案在 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 大约需要 1 毫秒,而 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;
      

      【讨论】:

        【解决方案9】:

        我对以上任何一个答案都不完全满意:Java 通常没有安装,dbms_crypto 需要授权,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_random 不是加密安全的,因此如果需要,请使用上面的 dbms_crypto 方法。十六进制值分布也被 translate 函数扭曲。 如果您需要真正的 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);
        

        【讨论】:

          【解决方案10】:

          我和我的一个朋友编写了一些纯 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;
          
          
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-05-22
            • 1970-01-01
            • 1970-01-01
            • 2010-10-05
            • 2021-06-24
            • 1970-01-01
            • 2021-11-21
            • 1970-01-01
            相关资源
            最近更新 更多