Memorizzazione degli indirizzi IP - varchar (45) vs varbinary (16)


11

Ho intenzione di creare una tabella con due campi - IDcome BIGINTe IPAddresscome uno varchar(45)o varbinary(16). L'idea è di memorizzare tutti gli indirizzi IP univoci e utilizzare un riferimento IDanziché l'attuale IP addressin altre tabelle.

Generalmente, ho intenzione di creare una procedura memorizzata che sta restituendo il IDdato IP addresso (se l'indirizzo non è stato trovato) inserisco l'indirizzo e restituisce il generato ID.

Mi aspetto di avere molti record (non posso dire esattamente quanti), ma ho bisogno che la procedura memorizzata sopra sia eseguita il più velocemente possibile. Quindi, mi chiedo come memorizzare l'indirizzo IP effettivo, in formato testo o byte. Quale sarà migliore?

Ho già scritto SQL CLRfunzioni per trasformare i byte dell'indirizzo IP in stringa e viceversa, quindi la trasformazione non è un problema (lavorare con entrambi IPv4e IPv6).

Immagino di dover creare un indice per ottimizzare la ricerca, ma non sono sicuro di dover includere il IP addresscampo nell'indice cluster o di creare un indice separato e con quale tipo la ricerca sarà più veloce?


2
Almeno per IPv4, perché non 4 minuscoli? Quindi sono in realtà leggibili dall'uomo e non è necessario eseguire alcuna conversione. È inoltre possibile creare tutti i tipi di colonne calcolate persistenti per rappresentare tipi specifici di ricerche (corrispondenza esatta, sottorete, ecc.).
Aaron Bertrand

Se così fosse, IPv4suppongo che convertissi l'indirizzo INTe usassi il campo come chiave indice. Ma perché IPv6ho bisogno di usare due BIGINTcampi e preferisco memorizzare il valore in un campo - mi sembra più naturale.
gotqn

1
Ancora non capisci perché INT invece di 4 TINYINTs? Stessa memoria, debug più semplice, meno sciocchezze, IMHO. Se hai due tipi completamente diversi con convalida e significato diversi, perché devono usare la stessa colonna? Se stai scommettendo che una singola colonna è più semplice, perché non usare solo SQL_VARIANT, allora non devi preoccuparti di nulla. Puoi memorizzare date, stringhe e numeri e tutti possono organizzare una grande festa in una colonna gigantesca e inutile ...
Aaron Bertrand

Da dove provengono gli indirizzi IP? Includeranno mai la maschera / sottorete (ovvero il 10.10.10.1/124)? Ho visto che ciò proviene dai registri del web server e non si traduce facilmente in BIGINT (INT non funzionerà poiché il calcolo richiede un INT senza segno, a meno che, naturalmente, non si incorpori che la normalizzazione presupponga che 0 sia in realtà -2,14xxxx miliardi). Immagino che la subnet mask possa essere solo un campo TINYINT aggiuntivo. Ma capisco che voglio archiviare come BIGINT se voglio abbinarlo fino a un DB di latitudine / longitudine per mapparli. Ma come ha detto Aaron, quello può essere un col computato persistente.
Solomon Rutzky,

Risposte:


12

come memorizzare l'indirizzo IP effettivo - in formato testo o byte. Quale sarà migliore?

Dato che qui "testo" si riferisce VARCHAR(45)e "byte" si riferisce VARBINARY(16), direi: nessuno dei due .

Date le seguenti informazioni (dall'articolo di Wikipedia su IPv6 ):

Rappresentazione dell'indirizzo
I 128 bit di un indirizzo IPv6 sono rappresentati in 8 gruppi di 16 bit ciascuno. Ogni gruppo è scritto come 4 cifre esadecimali e i gruppi sono separati da due punti (:). L'indirizzo 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329 è un esempio di questa rappresentazione.

Per comodità, un indirizzo IPv6 può essere abbreviato in notazioni più brevi applicando le seguenti regole, ove possibile.

  • Uno o più zeri iniziali da qualsiasi gruppo di cifre esadecimali vengono rimossi; questo di solito viene fatto a tutti o nessuno degli zero iniziali. Ad esempio, il gruppo 0042 viene convertito in 42.
  • Le sezioni consecutive di zero vengono sostituite da due punti (: :). I due punti possono essere usati solo una volta in un indirizzo, poiché l'uso multiplo renderebbe indeterminato l'indirizzo. RFC 5952 raccomanda di non utilizzare un doppio punto per indicare una singola sezione omessa di zero. [41]

Un esempio di applicazione di queste regole:

        Indirizzo iniziale: 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329
        Dopo aver rimosso tutti gli zeri iniziali in ciascun gruppo: 2001: db8: 0: 0: 0: ff00: 42: 8329
        Dopo aver omesso sezioni consecutive di zero: 2001 : db8 :: FF00: 42: 8329

Vorrei iniziare utilizzando 8 VARBINARY(2)campi per rappresentare gli 8 gruppi. I campi per i gruppi 5 - 8 dovrebbero essere NULLin quanto verranno utilizzati solo per gli indirizzi IPv6. I campi per i gruppi 1 - 4 devono essere NOT NULLcome verranno utilizzati per gli indirizzi IPv4 e IPv6.

Mantenendo ciascun gruppo indipendente (al contrario di loro combinazione in o un VARCHAR(45)o un VARBINARY(16)o anche due BIGINTcampi) si ottengono due vantaggi principali:

  1. È molto più facile ricostruire l'indirizzo in una particolare rappresentazione. Altrimenti, al fine di sostituire gruppi consecutivi di zero con (: :) dovresti analizzarlo. Tenerli separati consente semplici IF/ IIF/ CASEdichiarazioni per facilitare questo.
  2. Risparmierai un sacco di spazio sugli indirizzi IPv6 abilitando ROW COMPRESSIONo PAGE COMPRESSION. Poiché entrambi i tipi di COMPRESSIONE consentiranno che i campi 0x00occupino 0 byte, tutti questi gruppi di zero ora non ti costeranno nulla. D'altra parte, se hai archiviato l'indirizzo di esempio dall'alto (nella citazione di Wikipedia), i 3 set di tutti gli zeri nel mezzo occuperebbero tutto il loro spazio (a meno che tu non stia facendo il VARCHAR(45)e andassi con la notazione ridotta , ma potrebbe non funzionare bene per l'indicizzazione e richiederebbe un'analisi speciale per ricostruirlo nel formato completo, quindi supponiamo che non sia un'opzione ;-).

Se è necessario acquisire la rete, creare un TINYINTcampo per quello chiamato, um, [Network]:-)

Per ulteriori informazioni sul valore di rete, ecco alcune informazioni da un altro articolo di Wikipedia sull'indirizzo IPv6 :

reti

Una rete IPv6 utilizza un blocco di indirizzi che è un gruppo contiguo di indirizzi IPv6 di dimensioni pari a due. L'insieme iniziale di bit degli indirizzi è identico per tutti gli host di una determinata rete e viene chiamato indirizzo di rete o prefisso di routing .

Gli intervalli di indirizzi di rete sono scritti in notazione CIDR. Una rete è indicata dal primo indirizzo nel blocco (che termina in tutti gli zeri), una barra (/) e un valore decimale uguale alla dimensione in bit del prefisso. Ad esempio, la rete scritta come 2001: db8: 1234 :: / 48 inizia all'indirizzo 2001: db8: 1234: 0000: 0000: 0000: 0000: 0000 e termina al 2001: db8: 1234: ffff: ffff: ffff: ffff : ffff.

Il prefisso di routing di un indirizzo di interfaccia può essere indicato direttamente con l'indirizzo mediante notazione CIDR. Ad esempio, la configurazione di un'interfaccia con indirizzo 2001: db8: a :: 123 connesso alla sottorete 2001: db8: a :: / 64 è scritta come 2001: db8: a :: 123/64.


Per l'indicizzazione, direi di creare un indice non cluster sugli 8 campi Gruppo e possibilmente il campo Rete se decidi di includerlo.


Il risultato finale dovrebbe essere simile al seguente:

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

Appunti:

  • Riconosco che BIGINTprevedi di utilizzare per il campo ID, ma prevedi davvero di acquisire più di 4.294.967.295 valori univoci? In tal caso, modifica semplicemente il campo in BIGINT e puoi anche cambiare il valore del seme in 0. Ma altrimenti stai meglio usando INT e iniziando con il valore minimo in modo da poter utilizzare l'intero intervallo di quel tipo di dati .
  • Se lo si desidera, è possibile aggiungere una o più colonne calcolate NON presidiate a questa tabella per restituire rappresentazioni di testo dell'indirizzo IP.
  • I campi Gruppo * sono disposti intenzionalmente verso il basso , da 8 a 1, nella tabella in modo che SELECT *restituendo i campi nell'ordine previsto. Ma l'indice è loro andando in su , da 1 a 8, in quanto questo è il modo in cui vengono compilati.
  • Un esempio (non finito) di una colonna calcolata per rappresentare i valori in forma di testo è:

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

    Test:

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

    Risultato:

    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

Per SQL Server 2005, definire le colonne come VARDECIMALfinite VARBINARYpoiché DATA_COMPRESSIONnon è disponibile?
Matt

@SolomonRutzky Grazie per la spiegazione dettagliata. Sono curioso, come potrei cercare tra gli intervalli di indirizzi? Ad esempio, ho un fornitore di dati che fornisce dati di geolocalizzazione IP sotto forma di un indirizzo IP iniziale e finale. Devo trovare in quale intervallo rientra un determinato IP.
J Weezy,

@JWeezy Prego :). Come vengono memorizzati gli indirizzi IP di inizio e fine? Stai utilizzando indirizzi IPv4 o v6?
Solomon Rutzky,

@SolomonRutzky Both. IPv4 non è un problema perché posso memorizzarlo come numero intero. Sfortunatamente, non esiste un numero di dati a 128 bit intero o relativo al numero in SQL Server abbastanza grande da gestirlo. Quindi, per IPv6 lo sto memorizzando in VARBINARY (16) e quindi utilizzo l'operatore BETWEEN per cercare tra gli intervalli. Ma sto ottenendo più risultati su intervalli IP, che non credo siano corretti. Vorrei utilizzare lo stesso tipo di dati per IPv4 e IPv6, se possibile.
J Weezy,

@JWeezy stavo per suggerire BINARY(16);-). Potete per favore farmi un esempio con un intervallo di inizio / fine e almeno due righe che ricevi, una valida e almeno una non valida? È possibile che VARbinary accorcia alcuni valori.
Solomon Rutzky,

1

Più piccolo sarà sempre più veloce. Con valori più piccoli puoi inserirne più in una singola pagina, quindi meno IO, alberi B potenzialmente più superficiali ecc.

Ovviamente, tutte le altre cose (overhead di traduzione, leggibilità, compatibilità, carico della CPU, sargability dell'indice ecc.) Sono uguali.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.