15

在 Oracle 中,表示网络地址的适当数据类型或技术是什么,哪些地址可能是 IPv4 或 IPv6?

背景:我正在转换一个记录网络活动的表,该表使用 PostgreSQLinet数据类型构建,以将 v4 和 v6 地址保存在同一个表中。

但是,没有行同时包含 v4 和 v6 地址。(也就是说,一条记录要么来自机器的 v4 堆栈,要么来自机器的 v6 堆栈。)

4

7 回答 7

16

在 Oracle 中,表示网络地址的适当数据类型或技术是什么,哪些地址可能是 IPv4 或 IPv6

有两种方法:

  1. 仅存储。
  2. 存储常规表示

仅用于存储。IPV4 地址应该是一个整数(32 位就足够了)。对于 IP V6,128 位,INTEGER(类似于 Number(38))就可以了。当然,那是存储。该方法认为表示是应用程序的问题。

如果采取相反的策略,即存储常规表示,则需要确保 IP V4 和 IPV6 地址只有一种常规(字符串)表示。它以 ipV4 闻名。至于IPV6,也有标准格式。

我的偏好是第一种策略。在最坏的情况下,您可以采用混合方法(尽管不是酸性的)并将二进制和 ascii 表示并排存储,并以“优先级”为二进制值。

但是,没有行同时包含 v4 和 v6 地址。

IPV6 格式的 IPV4 地址的标准表示是:::ffff:192.0.2.128.

我不知道上下文,但我会保留两列,一列用于 IPV4,另一列用于不同的 ipV6 地址。

更新
在@sleepyMonad 的良好评论之后,我想指出的是,最好使用 INTEGER 数据类型而不是Number数据类型,这将很高兴地容纳可以用 128 位整数表示的最高可能值'ff...ff' (需要39 个十进制数字)。38 是从 0 到 9的 10 的最高幂,可以在 128 位上编码,但仍然可以为2**128 - 1插入最大无符号值(十进制 340282366920938463463374607431768211455)。这是一个小测试来说明这种可能性。

create table test (
  id integer primary key,
  ipv6_address_bin INTEGER );

-- Let's enter 2**128 - 1 in the nueric field
insert into test (id, ipv6_address_bin) values ( 1, to_number ( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') ) ;

-- retrieve it to make sure it's not "truncated".
select to_char ( ipv6_address_bin, 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' ) from test where id = 1 ;
-- yields 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'

select to_char ( ipv6_address_bin ) from test where id = 1 ;
-- yields 340282366920938463463374607431768211455

select LOG(2, ipv6_address_bin) from test where id = 1 ;
-- yields 128

select LOG(10, ipv6_address_bin) from test where id = 1 ;
-- yields > 38
于 2011-02-09T21:15:58.433 回答
8

将其存储在 RAW 中。

RAW 是变长字节数组,所以....

  • 只需将 IPv4 视为 4 个字节的数组
  • 和 IPv6 作为 16 个字节的数组

...并将其中任何一个直接存储在 RAW(16) 中。


RAW 可以被索引,可以是 PK、UNIQUE 或 FOREIGN KEY,因此您可以使用 VARCHAR2 或 INT/NUMBER/DECIMAL 执行通常可以执行的任何操作,但转换和存储开销更少。

为了说明 INT 相对于 RAW 的存储开销,请考虑以下示例:

CREATE TABLE IP_TABLE (
    ID INT PRIMARY KEY,
    IP_RAW RAW(16), 
    IP_INT INT
);

INSERT INTO IP_TABLE (ID, IP_RAW, IP_INT) VALUES (
    1,
    HEXTORAW('FFFFFFFF'),
    TO_NUMBER('FFFFFFFF', 'XXXXXXXX')
);

INSERT INTO IP_TABLE (ID, IP_RAW, IP_INT) VALUES (
    2,
    HEXTORAW('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'),
    TO_NUMBER('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
);

SELECT VSIZE(IP_RAW), VSIZE(IP_INT), IP_TABLE.*  FROM IP_TABLE;

结果(在 Oracle 10.2 下):

table IP_TABLE created.
1 rows inserted.
1 rows inserted.
VSIZE(IP_RAW)          VSIZE(IP_INT)          ID                     IP_RAW                           IP_INT                 
---------------------- ---------------------- ---------------------- -------------------------------- ---------------------- 
4                      6                      1                      FFFFFFFF                         4294967295             
16                     21                     2                      FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 340282366920938463463374607431768211455 
于 2012-02-03T03:11:49.413 回答
4

@Alain Pannetier(因为我还不能发表评论):根据http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/sql_elements001,ANSI INTEGER 数据类型映射到 Oracle 中的 NUMBER(38) .htm#i54335。在表格下方,您可以找到 NUMBER 仅提供 126 位二进制精度的信息,这对于 128 位 IPv6 地址来说是不够的。最大值可能存储得很好,但会有一些地址被舍入到下一个较低的地址。

内部数字格式为 ROUND((length(p)+s)/2))+1 ( http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/datatype.htm#i16209 ) .

更新:再次摆弄这个问题后,我现在找到了一种解决方案,可以对包含 IPv6 地址的网络进行高性能查询:将 IPv6 地址和子网掩码存储在 RAW(16) 列中,并使用 UTL_RAW.BIT_AND 进行比较:

SELECT name, DECODE(UTL_RAW.BIT_AND('20010DB8000000000000000000000001', ipv6_mask), ipv6_net, 1, 0)
FROM ip_net
WHERE ipv6_net IS NOT NULL;
于 2011-10-20T12:32:11.437 回答
1

您还可以使用自定义 oracle 对象。

SQL>set SERVEROUTPUT on
SQL>drop table test;

Table dropped.

SQL>drop type body inaddr;

Type body dropped.

SQL>drop type inaddr;

Type dropped.

SQL>create type inaddr as object
  2  ( /* TODO enter attribute and method declarations here */
  3  A number(5),
  4  B number(5),
  5  C number(5),
  6  D number(5),
  7  E number(5),
  8  F number(5),
  9  G number(5),
 10  H NUMBER(5),
 11  MAP MEMBER FUNCTION display RETURN VARCHAR2,
 12  MEMBER FUNCTION toString( SELF IN INADDR , CONTRACT BOOLEAN DEFAULT TRUE) RETURN VARCHAR2,
 13  CONSTRUCTOR FUNCTION INADDR(SELF IN OUT NOCOPY INADDR, INADDRASSTRING VARCHAR2)  RETURN SELF AS RESULT
 14  
 15  ) NOT FINAL;
 16  /

SP2-0816: Type created with compilation warnings

SQL>
SQL>
SQL>CREATE TYPE BODY INADDR AS
  2  
  3  MAP MEMBER FUNCTION display RETURN VARCHAR2
  4  IS BEGIN
  5  return tostring(FALSE);
  6  END;
  7  
  8  
  9  MEMBER FUNCTION TOSTRING( SELF IN  INADDR , CONTRACT BOOLEAN DEFAULT TRUE) RETURN VARCHAR2 IS
 10  IP4 VARCHAR2(6) := 'FM990';
 11  ip6 varchar2(6) := 'FM0XXX';
 12    BEGIN
 13  IF CONTRACT THEN
 14    ip6 := 'FMXXXX';
 15  end if;
 16  
 17  IF CONTRACT AND A =0 AND B=0 AND C = 0 AND D=0 AND E =0 AND F = 65535 THEN --ipv4
 18      RETURN  '::FFFF:'||TO_CHAR(TRUNC(G/256),'FM990.')||TO_CHAR(MOD(G,256),'FM990.')||TO_CHAR(TRUNC(H/256),'FM990.')||TO_CHAR(MOD(H,256),'FM990');
 19  ELSE
 20      RETURN
 21  TO_CHAR(A,ip6)||':'||
 22  TO_CHAR(B,IP6)||':'||
 23  TO_CHAR(C,ip6)||':'||
 24  TO_CHAR(D,ip6)||':'||
 25  TO_CHAR(E,ip6)||':'||
 26  TO_CHAR(F,ip6)||':'||
 27  TO_CHAR(G,ip6)||':'||
 28  TO_CHAR(H,ip6);
 29  end if;
 30    end;
 31  
 32      CONSTRUCTOR FUNCTION inaddr(SELF IN OUT NOCOPY inaddr, inaddrasstring VARCHAR2)
 33                                 RETURN SELF AS RESULT IS
 34      begin
 35          if instr(inaddrasstring,'.') > 0 then
 36            --ip4
 37  null;
 38              a := 0;
 39              B := 0;
 40              C := 0;
 41              D := 0;
 42              E := 0;
 43              F := TO_NUMBER('FFFF', 'XXXX');
 44              G := TO_NUMBER(TO_CHAR(TO_NUMBER(REGEXP_SUBSTR(INADDRASSTRING,'([0-9]{1,3}).',1,1,'i',1),'999'),'FM0X')
 45  ||TO_CHAR(TO_NUMBER(REGEXP_SUBSTR(INADDRASSTRING,'([0-9]{1,3}).',1,2,'i',1),'999'),'FM0X')
 46  ,'XXXX');
 47              h := TO_NUMBER(TO_CHAR(TO_NUMBER(REGEXP_SUBSTR(INADDRASSTRING,'([0-9]{1,3}).',1,3,'i',1),'999'),'FM0X')
 48  ||TO_CHAR(TO_NUMBER(REGEXP_SUBSTR(INADDRASSTRING,'([0-9]{1,3})',1,4,'i',1),'999'),'FM0X')
 49  ,'XXXX');
 50  
 51          ELSIF instr(inaddrasstring,':') > 0 then
 52              --ip6
 53              a := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,1,'i',1),'XXXX');
 54              b := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,2,'i',1),'XXXX');
 55              c := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,3,'i',1),'XXXX');
 56              d := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,4,'i',1),'XXXX');
 57              E := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,5,'i',1),'XXXX');
 58              f := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,6,'i',1),'XXXX');
 59              g := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,7,'i',1),'XXXX');
 60              H := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,8,'i',1),'XXXX');
 61          end if;
 62  
 63          RETURN;
 64      END;
 65  end;
 66  /

Type body created.

SQL>
SQL>create table test
  2  (id integer primary key,
  3  address inaddr);

Table created.

SQL>
SQL>select * from test;

no rows selected

SQL>
SQL>
SQL>insert into test values (1, INADDR('fe80:0000:0000:0000:0202:b3ff:fe1e:8329') );

1 row created.

SQL>INSERT INTO TEST VALUES (2, INADDR('192.0.2.128') );

1 row created.

SQL>insert into test values (3, INADDR('20.0.20.1') );

1 row created.

SQL>insert into test values (4, INADDR('fe80:0001:0002:0003:0202:b3ff:fe1e:8329') );

1 row created.

SQL>insert into test values (5, INADDR('fe80:0003:0002:0003:0202:b3ff:fe1e:8329') );

1 row created.

SQL>INSERT INTO TEST VALUES (6, INADDR('fe80:0003:0001:0003:0202:b3ff:fe1e:8329') );

1 row created.

SQL>INSERT INTO TEST VALUES (7, INADDR('fe80:0003:0001:0003:0202:b3ff:fe1e:8328') );

1 row created.

SQL>INSERT INTO TEST VALUES (8, INADDR('dead:beef:f00d:cafe:dea1:aced:b00b:1234') );

1 row created.

SQL>
SQL>COLUMN INET_ADDRESS_SHORT FORMAT A40
SQL>column inet_address_full format a40
SQL>
SQL>select t.address.toString() inet_address_short, t.address.display( ) inet_address_full
  2  from test T
  3  order by t.address ;

INET_ADDRESS_SHORT                       INET_ADDRESS_FULL
---------------------------------------- ----------------------------------------
::FFFF:20.0.20.1                         0000:0000:0000:0000:0000:FFFF:1400:1401
::FFFF:192.0.2.128                       0000:0000:0000:0000:0000:FFFF:C000:0280
DEAD:BEEF:F00D:CAFE:DEA1:ACED:B00B:1234  DEAD:BEEF:F00D:CAFE:DEA1:ACED:B00B:1234
FE80:0:0:0:202:B3FF:FE1E:8329            FE80:0000:0000:0000:0202:B3FF:FE1E:8329
FE80:1:2:3:202:B3FF:FE1E:8329            FE80:0001:0002:0003:0202:B3FF:FE1E:8329
FE80:3:1:3:202:B3FF:FE1E:8328            FE80:0003:0001:0003:0202:B3FF:FE1E:8328
FE80:3:1:3:202:B3FF:FE1E:8329            FE80:0003:0001:0003:0202:B3FF:FE1E:8329
FE80:3:2:3:202:B3FF:FE1E:8329            FE80:0003:0002:0003:0202:B3FF:FE1E:8329

8 rows selected.

SQL>spool off

我只是在最后一个小时把它放在一起(同时自学了一些东西),所以我确信它可以改进。如果我进行更新,我会在这里重新发布

于 2012-05-30T04:02:02.277 回答
1

我更喜欢将 IP 地址存储在字符串中,格式由 SYS_CONTEXT ('USERENV', 'IP_ADDRESS') 返回

在 11g 中对 SYS_CONTEXT 的引用仅将默认返回值长度描述为 256 字节,并且没有描述确切的“IP_ADDRESS”上下文的返回值大小。

在文档Oracle 数据库和 IPv6 方向声明中描述:

Oracle Database 11g 第 2 版支持 RFC2732 指定的标准 IPv6 地址表示法。一个 128 位的 IP 地址通常表示为 8 组,每组 4 个十六进制数字,以“:”符号作为组分隔符。每组中的前导零被删除。例如,1080:0:0:0:8:800:200C:417A 将是有效的 IPv6 地址。可以选择使用“::”分隔符压缩一个或多个连续的零字段。例如,1080::8:800:200C:417A。

从这个注释中,我更喜欢制作列IP_ADDRESS varchar2(39)以允许存储 8 个组,4 个数字和 7 个分隔符。

于 2014-03-12T10:07:09.187 回答
0

Oracle 文档确实声明 INTEGER 是 NUMBER(38) 的别名,但这可能是一个错字,因为它上面的段落指出:

NUMBER(p,s) 其中: p 是精度... Oracle 保证数字的可移植性,精度高达 20 个以 100 为基数的数字,这相当于 39 或 40 个十进制数字,具体取决于小数点的位置。

所以 NUMBER 可以存储 39 到 40 位数字,而 INTEGER 很可能是 NUMBER(max precision) 而不是 NUMBER(38) 的别名。这就是提供的示例有效的原因(如果您将 INTEGER 更改为 NUMBER 则有效)。

于 2012-02-03T02:07:25.660 回答
0

可能性是:

  • 存储为字符串,即VARCHAR2(示例1080::8:800:200c:417a
  • 存储为数值
    • NUMBER数据类型
    • INTEGER数据类型
  • 存储为RAW价值
    • 一个 RAW 值,即分别用于 IPv4 或RAW(4)IPv6RAW(16)
    • IPv4 或 IPv6 分别为4 xRAW(1)或 8 xRAW(2)

我建议使用RAW值,因为

  • 如果您使用字符串,那么您必须考虑不同格式的 IPv6。

    1080::8:800:200C:417A
    1080::8:800:200c:417a
    1080::8:800:32.12.65.122
    1080:0:0:0:8:800:200C:417A
    1080:0:0:0:0008:0800:200C:417A
    1080:0000:0000:0000:0008:0800:200C:417A
    

    是同一 IPv6 IP 地址的所有法律代表。您的应用程序需要强制使用通用格式才能正确使用,例如在WHERE条件下使用。

  • NUMBER/INTEGER如果不转换为人类可读的格式,值是毫无意义的。不能INTEGER在 PL/SQL 中使用数据类型

    i INTEGER := 2**128-1; -- i.e. ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
    
    -> ORA-06502: PL/SQL: numeric or value error: number precision too large. 
    
  • 如果您必须使用子网划分,则不能使用函数BITAND - 它还支持最多 2^127 的数字

  • 您可以使用UTL_RAW函数UTL_RAW.BIT_AND, UTL_RAW.BIT_COMPLEMENT,UTL_RAW.BIT_OR进行子网操作。

  • 如果您必须处理大量数据(我说的是数十亿行),将 IP-Address 拆分为几个 RAW 值(即 4 xRAW(1)或 8 x )可能是有益的RAW(2)。这样的列将被预定用于位图索引,您将节省大量磁盘空间并提高性能。

于 2018-05-31T07:12:47.047 回答