Je vais créer une table avec deux champs - ID
comme BIGINT
et IPAddress
comme varchar(45)
ou varbinary(16)
. L'idée est de stocker toutes les adresses IP uniques et d'utiliser une référence ID
à la place du IP address
Réel dans d'autres tableaux.
Généralement, je vais créer une procédure stockée qui renvoie le ID
pour un IP address
Donné ou (si l'adresse n'est pas trouvée) insérer l'adresse et renvoyer le ID
généré .
Je m'attends à avoir de nombreux enregistrements (je ne peux pas dire exactement combien), mais j'ai besoin que la procédure stockée ci-dessus soit exécutée le plus rapidement possible. Donc, je me demande comment stocker l'adresse IP réelle - au format texte ou octets. Qu'est-ce qui va être mieux?
J'ai déjà écrit des fonctions SQL CLR
Pour transformer les octets d'adresse IP en chaîne et inversement, donc la transformation n'est pas un problème (travailler avec à la fois IPv4
Et IPv6
).
Je suppose que je dois créer un index pour optimiser la recherche, mais je ne suis pas sûr de devoir inclure le champ IP address
À l'index clusterisé, ou de créer un index séparé et avec quel type la recherche sera plus rapide?
comment stocker l'adresse IP réelle - au format texte ou octets. Qu'est-ce qui va être mieux?
Puisque "texte" se réfère ici à VARCHAR(45)
et "octets" à VARBINARY(16)
, je dirais: ni .
Étant donné les informations suivantes (de article Wikipedia sur IPv6 ):
Représentation d'adresse
Les 128 bits d'une adresse IPv6 sont représentés en 8 groupes de 16 bits chacun. Chaque groupe est écrit en 4 chiffres hexadécimaux et les groupes sont séparés par des deux-points (:). L'adresse 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329 est un exemple de cette représentation.Pour plus de commodité, une adresse IPv6 peut être abrégée en notations plus courtes en appliquant les règles suivantes, si possible.
- Un ou plusieurs zéros de tête de n'importe quel groupe de chiffres hexadécimaux sont supprimés; cela est généralement fait pour tous ou aucun des zéros non significatifs. Par exemple, le groupe 0042 est converti en 42.
- Les sections consécutives de zéros sont remplacées par un double signe deux points (: :). Le double signe deux-points ne peut être utilisé qu'une seule fois dans une adresse, car une utilisation multiple rendrait l'adresse indéterminée. La RFC 5952 recommande de ne pas utiliser de deux-points pour indiquer une seule section omise de zéros. [41]
Un exemple d'application de ces règles:
Adresse initiale: 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329
Après avoir supprimé tous les zéros de tête de chaque groupe: 2001: db8: 0: 0: 0: ff00: 42: 8329
Après avoir omis des sections consécutives de zéros: 2001: db8 :: ff00: 42: 8329
Je commencerais par utiliser 8 champs VARBINARY(2)
pour représenter les 8 groupes. Les champs des groupes 5 à 8 doivent être NULL
car ils ne seront utilisés que pour les adresses IPv6. Les champs des groupes 1 à 4 doivent être NOT NULL
Car ils seront utilisés pour les adresses IPv4 et IPv6.
En gardant chaque groupe indépendant (au lieu de les combiner en une VARCHAR(45)
ou une VARBINARY(16)
ou même deux BIGINT
champs), vous obtenez deux avantages principaux:
IF
/IIF
/CASE
pour faciliter cela.ROW COMPRESSION
Ou PAGE COMPRESSION
. Étant donné que les deux types de COMPRESSION permettront aux champs qui sont 0x00
De prendre 0 octets, tous ces groupes de zéros ne vous coûteront plus rien. D'un autre côté, si vous stockez l'exemple d'adresse ci-dessus (dans la citation de Wikipedia), alors les 3 ensembles de tous les zéros au milieu prendraient leur pleine quantité d'espace (à moins que vous ne fassiez la VARCHAR(45)
et est allé avec la notation réduite, mais cela pourrait ne pas bien fonctionner pour l'indexation et nécessiterait une analyse spéciale pour la reconstruire au format complet, supposons donc que ce n'est pas une option ;-).SI vous devez capturer le réseau, créez un champ TINYINT
pour celui appelé, euh, [Network]
:-)
Pour plus d'informations sur la valeur du réseau, voici quelques informations d'un autre article Wikipedia sur l'adresse IPv6 :
Réseaux
Un réseau IPv6 utilise un bloc d'adresses qui est un groupe contigu d'adresses IPv6 d'une taille qui est une puissance de deux. Le premier ensemble de bits des adresses est identique pour tous les hôtes d'un réseau donné et est appelé l'adresse du réseau ou le préfixe de routage .
Les plages d'adresses réseau sont écrites en notation CIDR. Un réseau est indiqué par la première adresse du bloc (se terminant par tous les zéros), une barre oblique (/) et une valeur décimale égale à la taille en bits du préfixe. Par exemple, le réseau écrit comme 2001: db8: 1234 ::/48 commence à l'adresse 2001: db8: 1234: 0000: 0000: 0000: 0000: 0000 et se termine à 2001: db8: 1234: ffff: ffff: ffff: ffff : ffff.
Le préfixe de routage d'une adresse d'interface peut être directement indiqué avec l'adresse par notation CIDR. Par exemple, la configuration d'une interface avec l'adresse 2001: db8: a :: 123 connectée au sous-réseau 2001: db8: a ::/64 est écrite comme 2001: db8: a :: 123/64.
Pour l'indexation, je dirais créer un index non clusterisé sur les 8 champs Groupe, et éventuellement le champ Réseau si vous décidez de l'inclure.
Le résultat final devrait ressembler à ceci:
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);
Remarques:
BIGINT
pour le champ ID, mais vous attendez-vous vraiment à capturer plus de 4 294 967 295 valeurs uniques? Si c'est le cas, changez simplement le champ en BIGINT et vous pouvez même changer la valeur de départ en 0. Mais sinon, vous feriez mieux d'utiliser INT et de commencer par la valeur minimale afin de pouvoir utiliser toute la plage de ce type de données .SELECT *
Renvoie les champs dans l'ordre attendu. Mais l'index les fait monter vers le haut , de 1 à 8, car c'est ainsi qu'ils sont remplis.Un exemple (inachevé) d'une colonne calculée pour représenter les valeurs sous forme de texte est:
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
);
Tester:
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];
Résultat:
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
Plus petit sera toujours plus rapide. Avec des valeurs plus petites, vous pouvez en insérer plus sur une seule page, donc moins d'E/S, des arbres B potentiellement moins profonds, etc.
Toutes autres choses (surcharge de traduction, lisibilité, compatibilité, charge CPU, sargabilité d'index, etc.) étant bien entendu égales.