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


11

我要创建一个表有两个领域- ID作为BIGINTIPAddress作为两种varchar(45)varbinary(16)。想法是存储所有唯一的IP地址,并使用引用ID代替IP address其他表中的实际IP 。

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

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

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

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


2
至少对于IPv4,为什么不使用4 tinyints?然后,它们实际上是人类可读的,并且您无需执行任何转换。您还可以创建各种持久化的计算列,以表示特定类型的搜索(完全匹配,子网等)。
亚伦·伯特兰

如果只是这种情况,IPv4我想我会将地址转换为INT并将字段用作索引键。但是因为IPv6我需要使用两个BIGINT字段,并且我更喜欢将值存储在一个字段中-在我看来似乎更自然。
gotqn

1
还是不明白为什么要用INT而不是4个TINYINT?相同的存储,更容易的调试,更少的废话,恕我直言。如果您有两种完全不同的类型,且具有不同的验证和含义,那么为什么它们需要使用同一列?如果您认为单列更简单,那么为什么不只使用SQL_VARIANT,那么您就不必担心任何事情。您可以存储日期,字符串和数字,每个人都可以在一个巨大的无用列中举办大型聚会……
亚伦·伯特兰

IP地址从哪里来?它们是否会包含掩码/子网(即10.10.10.1/124)?我已经看到这是从Web服务器日志中获取的,并且不会轻易转换为BIGINT(因为计算需要一个无符号INT,所以INT无法工作,除非您将归一化假设为0实际为-2.14xxxx billion)。我想子网掩码可能只是另外一个TINYINT字段。但是我确实理解如果想要将其存储为纬度/经度数据库以将其映射出来,我想将其存储为BIGINT。但是正如亚伦(Aaron)所说,这可以是一个持久的计算列。
所罗门·

Answers:


12

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

由于“文本”在这里指的是VARCHAR(45)和“字节”指的是VARBINARY(16),我会说:没有

给出以下信息(来自Wikipedia上有关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为该字段创建一个域,um,[Network]:-)

有关Network值的更多信息,以下是另一篇有关IPv6地址的Wikipedia文章中的信息:

网路

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

网络地址范围以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);

笔记:

  • 我知道您打算使用BIGINTID字段,但是您真的希望捕获超过4,294,967,295个唯一值吗?如果是这样,则只需将字段更改为BIGINT,然后甚至可以将种子值更改为0。但是否则,最好使用INT并从最小值开始,以便可以使用该数据类型的整个范围。
  • 如果需要,可以向该表中添加一个或多个“非持久计算列”以返回IPAddress的文本表示形式。
  • 该集团*场被布置为有意要下来,从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
    );

    测试:

    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];

    结果:

    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

对于SQL Server 2005,由于无法使用而将列定义为VARDECIMAL结束吗?VARBINARYDATA_COMPRESSION
马特

@SolomonRutzky谢谢您的详细解释。我很好奇,如何在地址范围之间进行搜索?例如,我有一个数据供应商,以起始IP地址和结束IP地址的形式提供IP地理位置数据。我需要找到该范围内的指定IP落在英寸
ĴWeezy

@JWeezy不客气:)。起始和结束IP地址如何存储?您使用的是IPv4还是v6地址?
所罗门·鲁兹基

@SolomonRutzky两者。IPv4没问题,因为我可以将其存储为整数。不幸的是,SQL Server中没有足够的128位整数或数字相关数据类型来处理它。因此,对于IPv6,我将其存储在VARBINARY(16)中,然后使用BETWEEN运算符在范围之间进行搜索。但是,我在IP范围上得到了多个结果,我认为这是不正确的。如果可能,我想对IPv4和IPv6使用相同的数据类型。
J Weezy

@JWeezy我会建议BINARY(16);-)。您能给我一个示例,其中包含一个开始/结束范围,并且至少返回两行,一个有效行,至少一个无效行吗?VARbinary可能会缩短某些值。
所罗门·鲁兹基

1

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

当然,所有其他条件(翻译开销,可读性,兼容性,CPU负载,索引可保存性等)都是相等的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.