it-swarm.com.de

Speichern von IP-Adressen - varchar (45) vs varbinary (16)

Ich werde eine Tabelle mit zwei Feldern erstellen - ID als BIGINT und IPAddress als varchar(45) oder varbinary(16). Die Idee ist, alle eindeutigen IP-Adressen zu speichern und stattdessen eine Referenz ID anstelle des tatsächlichen IP address In anderen Tabellen zu verwenden.

Im Allgemeinen werde ich eine gespeicherte Prozedur erstellen, die das ID für das angegebene IP address Zurückgibt, oder (falls die Adresse nicht gefunden wurde) die Adresse einfügen und das generierte ID zurückgeben .

Ich erwarte viele Datensätze (ich kann nicht genau sagen, wie viele), aber ich muss die oben gespeicherte Prozedur so schnell wie möglich ausführen. Ich frage mich also, wie ich die tatsächliche IP-Adresse speichern soll - im Text- oder Byte-Format. Welches wird besser?

Ich habe bereits SQL CLR - Funktionen zum Transformieren von IP-Adressbytes in Zeichenfolgen und umgekehrt geschrieben, sodass die Transformation kein Problem darstellt (sowohl mit IPv4 Als auch mit IPv6).

Ich denke, ich muss einen Index erstellen, um die Suche zu optimieren, aber ich bin nicht sicher, ob ich das Feld IP address In den Clustered-Index aufnehmen oder einen separaten Index erstellen soll und mit welchem ​​Typ die Suche schneller sein wird.

11
gotqn

speichern der tatsächlichen IP-Adresse - im Text- oder Byteformat. Welches wird besser?

Da sich "Text" hier auf VARCHAR(45) und "Bytes" auf VARBINARY(16) bezieht, würde ich sagen: weder.

Angesichts der folgenden Informationen (aus Wikipedia-Artikel zu IPv6 ):

Adressdarstellung
Die 128 Bits einer IPv6-Adresse werden in 8 Gruppen zu je 16 Bit dargestellt. Jede Gruppe besteht aus 4 hexadezimalen Ziffern und die Gruppen sind durch Doppelpunkte (:) getrennt. Die Adresse 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329 ist ein Beispiel für diese Darstellung.

Der Einfachheit halber kann eine IPv6-Adresse nach Möglichkeit durch Anwendung der folgenden Regeln zu kürzeren Notationen abgekürzt werden.

  • Eine oder mehrere führende Nullen aus einer beliebigen Gruppe von hexadezimalen Ziffern werden entfernt. Dies wird normalerweise entweder für alle oder keine der führenden Nullen durchgeführt. Beispielsweise wird die Gruppe 0042 in 42 konvertiert.
  • Aufeinanderfolgende Abschnitte von Nullen werden durch einen Doppelpunkt (: :) ersetzt. Der Doppelpunkt darf in einer Adresse nur einmal verwendet werden, da eine Mehrfachverwendung die Adresse unbestimmt machen würde. RFC 5952 empfiehlt, dass kein Doppelpunkt verwendet werden darf, um einen ausgelassenen einzelnen Abschnitt von Nullen zu kennzeichnen. [41]

Ein Beispiel für die Anwendung dieser Regeln:

Anfangsadresse: 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329
Nach dem Entfernen aller führenden Nullen in jeder Gruppe: 2001: db8: 0: 0: 0: ff00: 42: 8329
Nach dem Weglassen aufeinanderfolgender Abschnitte von Nullen: 2001: db8 :: ff00: 42: 8329

Ich würde damit beginnen, 8 VARBINARY(2) Felder zu verwenden, um die 8 Gruppen darzustellen. Die Felder für die Gruppen 5 bis 8 sollten NULL sein, da sie nur für IPv6-Adressen verwendet werden. Die Felder für die Gruppen 1 bis 4 sollten NOT NULL Sein, da sie sowohl für IPv4- als auch für IPv6-Adressen verwendet werden.

Indem Sie jede Gruppe unabhängig halten (anstatt sie entweder in ein VARCHAR(45) oder ein VARBINARY(16) oder sogar zwei BIGINT Felder zu kombinieren), erhalten Sie zwei Hauptvorteile:

  1. Es ist viel einfacher, die Adresse in eine bestimmte Darstellung zu rekonstruieren. Andernfalls müssten Sie es analysieren, um aufeinanderfolgende Gruppen von Nullen durch (: :) zu ersetzen. Wenn Sie sie getrennt halten, können Sie einfach IF/IIF/CASE Anweisungen verwenden, um dies zu erleichtern.
  2. Sie sparen eine Menge Speicherplatz bei IPv6-Adressen, indem Sie entweder ROW COMPRESSION Oder PAGE COMPRESSION Aktivieren. Da bei beiden Arten der KOMPRESSION Felder mit 0x00 0 Byte belegen können, kosten alle diese Nullengruppen jetzt nichts mehr. Wenn Sie dagegen die Beispieladresse von oben (im Wikipedia-Zitat) gespeichert haben, nehmen die drei Sätze aller Nullen in der Mitte ihren vollen Speicherplatz ein (es sei denn, Sie haben VARCHAR(45) ausgeführt). und ging mit der reduzierten Notation, aber das könnte für die Indizierung nicht gut funktionieren und würde spezielle Analyse erfordern, um es in das volle Format zu rekonstruieren, also nehmen wir an, dass dies keine Option ist ;-).

Wenn Sie das Netzwerk erfassen müssen, erstellen Sie ein TINYINT Feld für das Feld mit dem Namen, ähm, [Network] :-)

Weitere Informationen zum Netzwerkwert finden Sie hier in einem anderen Wikipedia-Artikel zur IPv6-Adresse :

Netzwerke

Ein IPv6-Netzwerk verwendet einen Adressblock, bei dem es sich um eine zusammenhängende Gruppe von IPv6-Adressen mit einer Zweierpotenz handelt. Der führende Satz von Bits der Adressen ist für alle Hosts in einem bestimmten Netzwerk identisch und wird als Netzwerkadresse oder Routing Präfix bezeichnet.

Netzwerkadressbereiche werden in CIDR-Notation geschrieben. Ein Netzwerk wird durch die erste Adresse im Block (die mit allen Nullen endet), einen Schrägstrich (/) und einen Dezimalwert angegeben, der der Größe des Präfixes in Bits entspricht. Das als 2001: db8: 1234 ::/48 geschriebene Netzwerk beginnt beispielsweise mit der Adresse 2001: db8: 1234: 0000: 0000: 0000: 0000: 0000 und endet mit 2001: db8: 1234: ffff: ffff: ffff: ffff : ffff.

Das Routing-Präfix einer Schnittstellenadresse kann direkt mit der Adresse durch CIDR-Notation angegeben werden. Beispielsweise wird die Konfiguration einer Schnittstelle mit der Adresse 2001: db8: a :: 123, die mit dem Subnetz 2001: db8: a ::/64 verbunden ist, als 2001: db8: a :: 123/64 geschrieben.


Für die Indizierung würde ich sagen, dass Sie einen nicht gruppierten Index für die 8 Gruppenfelder und möglicherweise das Netzwerkfeld erstellen, wenn Sie dies einschließen möchten.


Das Endergebnis sollte ungefähr so ​​aussehen:

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

Anmerkungen:

  • Ich erkenne, dass Sie vorhaben, BIGINT für das ID-Feld zu verwenden, aber erwarten Sie wirklich, mehr als 4.294.967.295 eindeutige Werte zu erfassen? Wenn ja, ändern Sie einfach das Feld in BIGINT und Sie können sogar den Startwert in 0 ändern. Andernfalls ist es besser, INT zu verwenden und mit dem Mindestwert zu beginnen, damit Sie den gesamten Bereich dieses Datentyps nutzen können .
  • Falls gewünscht, können Sie dieser Tabelle eine oder mehrere nicht persistierte berechnete Spalten hinzufügen, um Textdarstellungen der IPAddress zurückzugeben.
  • Die Felder der Gruppe * sind absichtlich so angeordnet, dass nach unten von 8 bis 1 in der Tabelle angezeigt wird, sodass bei Ausführung von SELECT * Die Felder in der erwarteten Reihenfolge zurückgegeben werden. Aber der Index lässt sie up von 1 bis 8 gehen, da sie so ausgefüllt werden.
  • Ein Beispiel (unvollendet) einer berechneten Spalte zur Darstellung der Werte in Textform ist:

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

    Prüfung:

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

    Ergebnis:

    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

Kleiner wird immer schneller sein. Mit kleineren Werten können Sie mehr davon in eine einzelne Seite einfügen, daher weniger E/A, möglicherweise flachere B-Bäume usw.

Alle anderen Dinge (Übersetzungsaufwand, Lesbarkeit, Kompatibilität, CPU-Auslastung, Index-Sargabilität usw.) sind natürlich gleich.

1
Michael Green