存储 IP 地址 - varchar(45) 与 varbinary(16)

got*_*tqn 12 database-design sql-server sql-server-2012 sql-clr nonclustered-index

我将创建一个包含两个字段的表 - IDasBIGINTIPAddressasvarchar(45)或者varbinary(16)。这个想法是存储所有唯一的 IP 地址并使用引用ID而不是IP address其他表中的实际地址。

通常,我将创建一个存储过程,该过程返回ID给定的IP address或(如果未找到地址)插入地址并返回生成的ID.

我期望有很多记录(我无法确切说出有多少),但我需要尽快执行上面的存储过程。所以,我想知道如何以文本或字节格式存储实际的 IP 地址。哪个会更好?

我已经编写了SQL CLR用于将 IP 地址字节转换为字符串和反向转换的函数,因此转换不是问题(同时使用IPv4IPv6)。

我想我需要创建一个索引来优化搜索,但我不确定我应该将该IP address字段包含在聚集索引中,还是创建一个单独的索引以及使用哪种类型的搜索会更快?

Sol*_*zky 13

如何存储实际 IP 地址 - 以文本或字节格式。哪个会更好?

由于这里的“文本”是指VARCHAR(45)而“字节”是指VARBINARY(16),我会说:两者都不是

鉴于以下信息(来自关于 IPv6 的维基百科文章):

地址表示
IPv6 地址的 128 位表示为 8 组,每组 16 位。每个组都写为 4 个十六进制数字,组之间用冒号 (:) 分隔。地址 2001:0db8:0000:0000:0000:ff00:0042:8329 就是这种表示的一个例子。

为方便起见,在可能的情况下,可以通过应用以下规则将 IPv6 地址缩写为较短的符号。

  • 从任何一组十六进制数字中删除一个或多个前导零;这通常对所有前导零或没有前导零进行。例如,组 0042 转换为 42。
  • 连续的零部分被替换为双冒号 (::)。双冒号在一个地址中只能使用一次,因为多次使用会使地址不确定。RFC 5952 建议不得使用双冒号来表示省略的单个零部分。 [41]

这些规则的应用示例:

        初始地址:2001:0db8:0000:0000:0000:ff00:0042:8329
        去除每组中所有前导零后:2001:db8:0:0:0:ff00:42:8329
        省略连续部分零后:2001 :db8::ff00:42:8329

我将首先使用 8 个VARBINARY(2)字段来表示 8 个组。组 5 - 8 的字段应该是NULL因为它们将仅用于 IPv6 地址。组 1 - 4 的字段应该NOT NULL与它们将用于 IPv4 和 IPv6 地址一样。

通过保持每个组独立(而不是将它们组合到一个VARCHAR(45)或一个VARBINARY(16)甚至两个BIGINT字段中),您可以获得两个主要好处:

  1. 将地址重构为任何特定表示要容易得多。否则,为了用 (::) 替换连续的零组,您必须解析它。让他们单独允许简单IF/ IIF/CASE语句来促成。
  2. 通过启用ROW COMPRESSION或 ,您将在 IPv6 地址上节省大量空间PAGE COMPRESSION。由于这两种类型的 COMPRESSION 都允许0x00占用 0 字节的字段,因此所有这些零组现在不会花费您任何费用。另一方面,如果您存储了上面的示例地址(在 Wikipedia 引用中),那么中间的 3 组全零将占用它们的全部空间(除非您这样做VARCHAR(45)并使用简化的符号,但这可能不适用于索引,并且需要特殊解析才能将其重建为完整格式,因此我们假设这不是一个选项;-)。

如果您需要捕获网络,请TINYINT为该字段创建一个字段,嗯,[Network]:-)

有关网络值的更多信息,请参阅维基百科一篇关于 IPv6 地址的文章

网络

IPv6 网络使用的地址块是大小为 2 的幂的连续 IPv6 地址组。对于给定网络中的所有主机,地址的前导位集是相同的,称为网络地址或路由前缀

网络地址范围以 CIDR 表示法编写。网络由块中的第一个地址(以全零结尾)、一个斜杠 (/) 和一个十进制值表示,该值等于前缀的位大小。例如,写成 2001:db8:1234::/48 的网络从地址 2001:db8:1234:0000:0000:0000:0000:0000 开始,到 2001:db8:1234:ffff:ffff:ffff:ffff 结束:ffff。

接口地址的路由前缀可以直接用CIDR表示法用地址表示。例如,地址为 2001:db8:a::123 的接口连接到子网 2001:db8:a::/64 的配置写为 2001:db8:a::123/64。


对于索引,我会说在 8 个组字段上创建一个非聚集索引,如果您决定包含它,可能还有网络字段。


最终结果应类似于以下内容:

CREATE TABLE [IPAddress]
(
  IPAddressID INT          NOT NULL IDENTITY(-2147483648, 1),
  Group8      VARBINARY(2) NULL, -- IPv6 only, NULL for IPv4
  Group7      VARBINARY(2) NULL, -- IPv6 only, NULL for IPv4
  Group6      VARBINARY(2) NULL, -- IPv6 only, NULL for IPv4
  Group5      VARBINARY(2) NULL, -- IPv6 only, NULL for IPv4
  Group4      VARBINARY(2) NOT NULL, -- both
  Group3      VARBINARY(2) NOT NULL, -- both
  Group2      VARBINARY(2) NOT NULL, -- both
  Group1      VARBINARY(2) NOT NULL, -- both
  Network     TINYINT      NULL
);

ALTER TABLE [IPAddress]
  ADD CONSTRAINT [PK_IPAddress]
  PRIMARY KEY CLUSTERED
  (IPAddressID ASC)
  WITH (FILLFACTOR = 100, DATA_COMPRESSION = PAGE);

CREATE NONCLUSTERED INDEX [IX_IPAddress_Groups]
  ON [IPAddress] (Group1 ASC, Group2 ASC, Group3 ASC, Group4 ASC,
         Group5 ASC, Group6 ASC, Group7 ASC, Group8 ASC, Network ASC)
  WITH (FILLFACTOR = 100, DATA_COMPRESSION = PAGE);
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 我知道您计划BIGINT用于 ID 字段,但您真的希望捕获超过 4,294,967,295 个唯一值吗?如果是这样,那么只需将字段更改为 BIGINT,然后您甚至可以将种子值更改为 0。但否则您最好使用 INT 并从最小值开始,以便您可以使用该数据类型的整个范围.
  • 如果需要,您可以向该表添加一个或多个非持久性计算列以返回 IP 地址的文本表示。
  • 该集团*场被布置为有意要下来,从8比1,表中,这样做SELECT *会在预期的顺序返回的字段。但是索引使它们从 1上升到 8,因为它们就是这样填写的。
  • 以文本形式表示值的计算列的示例(未完成)是:

    ALTER TABLE [IPAddress]
      ADD TextAddress AS (
    IIF([Group8] IS NULL,
        -- IPv4
        CONCAT(CONVERT(TINYINT, [Group4]), '.', CONVERT(TINYINT, [Group3]), '.',
          CONVERT(TINYINT, [Group2]), '.', CONVERT(TINYINT, [Group1]),
          IIF([Network] IS NOT NULL, CONCAT('/', [Network]), '')),
        -- IPv6
        LOWER(CONCAT(
          CONVERT(VARCHAR(4), [Group8], 2), ':', CONVERT(VARCHAR(4), [Group7], 2), ':',
          CONVERT(VARCHAR(4), [Group6], 2), ':', CONVERT(VARCHAR(4), [Group5], 2), ':',
          CONVERT(VARCHAR(4), [Group4], 2), ':', CONVERT(VARCHAR(4), [Group3], 2), ':',
          CONVERT(VARCHAR(4), [Group2], 2), ':', CONVERT(VARCHAR(4), [Group1], 2),
          IIF([Network] IS NOT NULL, CONCAT('/', [Network]), '')
         ))
       ) -- end of IIF
    );
    
    Run Code Online (Sandbox Code Playgroud)

    测试:

    INSERT INTO IPAddress VALUES (127, 0, 0, 0, 4, 22, 222, 63, NULL); -- IPv6
    INSERT INTO IPAddress VALUES (27, 10, 1234, 0, 45673, 200, 1, 6363, 48); -- IPv6
    INSERT INTO IPAddress VALUES (NULL, NULL, NULL, NULL, 192, 168, 2, 63, NULL); -- v4
    INSERT INTO IPAddress VALUES (NULL, NULL, NULL, NULL, 192, 168, 137, 29, 16); -- v4
    
    SELECT [IPAddressID], [Group8], [Group1], [Network], [TextAddress]
    FROM IPAddress ORDER BY [IPAddressID];
    
    Run Code Online (Sandbox Code Playgroud)

    结果:

    IPAddressID   Group8   Group1   Network  TextAddress
    -----------   ------   ------   -------  ---------------------
    -2147483646   0x007F   0x003F   NULL     007f:0000:0000:0000:0004:0016:00de:003f
    -2147483645   0x001B   0x18DB   48       001b:000a:04d2:0000:b269:00c8:0001:18db/48
    -2147483644   NULL     0x003F   NULL     192.168.2.63
    -2147483643   NULL     0x001D   16       192.168.137.29/16
    
    Run Code Online (Sandbox Code Playgroud)


Mic*_*een 2

越小总是会更快。使用较小的值,您可以将更多的内容放入单个页面中,从而减少 IO,可能会出现更浅的 B 树等。

当然,所有其他因素(翻译开销、可读性、兼容性、CPU 负载、索引控制能力等)都是相同的。