web-dev-qa-db-ja.com

IPアドレスの保存-varchar(45)とvarbinary(16)

IDBIGINTとして、IPAddressvarchar(45)またはvarbinary(16)として、2つのフィールドを持つテーブルを作成します。アイデアは、すべての一意のIPアドレスを保存し、他のテーブルの実際のIP addressの代わりに参照IDを使用することです。

通常、与えられたIP addressIDを返す、または(アドレスが見つからなかった場合)アドレスを挿入して生成されたIDを返すストアドプロシージャを作成します。

多くのレコードがあることを期待しています(正確な数はわかりません)が、上記のストアドプロシージャをできるだけ速く実行する必要があります。それで、実際のIPアドレスをテキストまたはバイト形式で保存する方法を知りたいです。どっちがいいの?

IPアドレスのバイトを文字列およびその逆に変換するためのSQL CLR関数をすでに作成しているため、変換は問題ではありません(IPv4IPv6の両方で機能します)。

検索を最適化するためにインデックスを作成する必要があると思いますが、クラスター化されたインデックスにIP addressフィールドを含める必要があるか、または別のインデックスを作成し、どのタイプで検索を高速化するべきかわかりませんか?

11
gotqn

実際のIPアドレスを格納する方法-テキストまたはバイト形式。どっちがいいの?

ここでの「テキスト」はVARCHAR(45)を指し、「バイト」はVARBINARY(16)を指しているため、どちらでもないと言います。

以下の情報が与えられた場合( IPv6に関するウィキペディアの記事 から):

住所表現
IPv6アドレスの128ビットは、それぞれ16ビットの8つのグループで表されます。各グループは4桁の16進数として記述され、グループはコロン(:)で区切られます。アドレス2001:0db8:0000:0000:0000:ff00:0042:8329は、この表現の例です。

便宜上、可能であれば、次の規則を適用することにより、IPv6アドレスを短い表記に短縮できます。

  • 16進数のグループから1つ以上の先行ゼロが削除されます。これは通常、先行ゼロのすべてに対して行われるか、まったく行われません。たとえば、グループ0042は42に変換されます。
  • ゼロの連続セクションは、二重コロン(::)に置き換えられます。二重コロンは、複数回使用するとアドレスが不確定になるため、アドレスで1回だけ使用できます。 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つのグループを表すために8つのVARBINARY(2)フィールドを使用します。グループ5〜8のフィールドはNULLである必要があります。これらのフィールドはIPv6アドレスにのみ使用されるためです。グループ1〜4のフィールドは、IPv4アドレスとIPv6アドレスの両方に使用されるため、NOT NULLである必要があります。

VARCHAR(45)またはVARBINARY(16)または2つのBIGINTフィールドに結合するのとは対照的に)各グループを独立させることにより、2つの主な利点が得られます。

  1. アドレスを特定の表現に再構築する方がはるかに簡単です。それ以外の場合、連続するゼロのグループを(::)で置き換えるには、解析する必要があります。それらを別々にしておくと、簡単なIF/IIF/CASEステートメントでこれを容易にすることができます。
  2. ROW COMPRESSIONまたはPAGE COMPRESSIONのいずれかを有効にすると、IPv6アドレスのスペースを大幅に節約できます。どちらのタイプのCOMPRESSIONでも、0x00のフィールドが0バイトを占めることを許可するため、これらのゼロのグループのすべてでコストが発生することはありません。一方、上記の例のアドレスを(Wikipediaの引用で)保存した場合、中央にあるすべてのゼロの3セットは、スペースをすべて使用します(VARCHAR(45)を実行している場合を除く)と表記を減らしましたが、それはインデックス作成にはうまく機能しない可能性があり、完全な形式に再構築するために特別な解析が必要になるので、それはオプションではないと仮定しましょう;-).

ネットワークをキャプチャする必要がある場合は、呼び出されたum、[Network]TINYINTフィールドを作成します:-)

ネットワーク値の詳細については、別の情報をご覧ください IPv6アドレスに関するウィキペディアの記事

ネットワーク

IPv6ネットワークは、2の累乗のサイズのIPv6アドレスの連続したグループであるアドレスブロックを使用します。アドレスの先行ビットセットは、特定のネットワーク内のすべてのホストで同一であり、ネットワークのアドレスまたはルーティングprefixと呼ばれます。

ネットワークアドレス範囲はCIDR表記で記述されます。ネットワークは、ブロック内の最初のアドレス(すべてゼロで終わる)、スラッシュ(/)、およびプレフィックスのビット単位のサイズに等しい10進値で示されます。たとえば、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);

ノート:

  • IDフィールドにBIGINTを使用する予定ですが、4,294,967,295を超える一意の値をキャプチャすることを本当に期待していますか?その場合は、フィールドをBIGINTに変更するだけで、シード値を0に変更することもできます。ただし、それ以外の場合は、INTを使用し、最小値から始めて、そのデータ型の範囲全体を利用できるようにする方がよいでしょう。 。
  • 必要に応じて、1つ以上のNONpersisted Computed Columnsをこのテーブルに追加して、IPAddressのテキスト表現を返すことができます。
  • Group *フィールドは、テーブル内でdownの8から1に意図的に配置されているため、SELECT *を実行すると、フィールドが期待どおりの順序で返されます。しかし、インデックスには、1から8までupがあり、そのように入力されます。
  • テキスト形式で値を表す計算列の例(未完成)は次のとおりです。

    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
    
13
Solomon Rutzky

小さいほど常に高速になります。値が小さいと、より多くの値を1つのページに収めることができるため、IOが少なくなり、Bツリーが浅くなる可能性があります。

もちろん、他のすべてのもの(翻訳のオーバーヘッド、読みやすさ、互換性、CPU負荷、インデックスの検索可能性など)は同じです。

1
Michael Green