Хранение IP-адресов - varchar (45) против varbinary (16)


11

Я собираюсь создать таблицу с двумя полями - IDкак BIGINTи IPAddressкак либо varchar(45)или varbinary(16). Идея состоит в том, чтобы сохранить все уникальные IP-адреса и использовать ссылку IDвместо фактической IP addressв других таблицах.

Как правило, я собираюсь создать хранимую процедуру, которая возвращает IDзаданный IP addressили (если адрес не был найден) вставить адрес и вернуть сгенерированный ID.

Я ожидаю иметь много записей (я не могу точно сказать, сколько), но мне нужно, чтобы хранимая процедура, описанная выше, была выполнена как можно быстрее. Итак, мне интересно, как сохранить фактический IP-адрес - в текстовом или байтовом формате. Что будет лучше?

Я уже написал SQL CLRфункции для преобразования байтов IP-адреса в строку и наоборот, поэтому преобразование не является проблемой (работа с обоими IPv4и IPv6).

Я думаю, мне нужно создать индекс для оптимизации поиска, но я не уверен, стоит ли включать IP addressполе в кластеризованный индекс или создавать отдельный индекс и с каким типом поиск будет быстрее?


2
По крайней мере, для IPv4, почему не 4 крошечных? Тогда они на самом деле читаются человеком, и вам не нужно выполнять какие-либо преобразования. Вы также можете создавать все виды постоянных вычисляемых столбцов для представления определенных типов поиска (точное совпадение, подсеть и т. Д.).
Аарон Бертран

Если бы это было только потому, что, IPv4я думаю, я бы преобразовал адрес INTи использовал поле в качестве ключа индекса. Но для IPv6меня нужно использовать два BIGINTполя, и я предпочитаю хранить значение в одном поле - мне кажется более естественным.
получил

1
До сих пор не понимаю, почему INT вместо 4 TINYINTs? Такое же хранилище, более простая отладка, меньше глупостей, ИМХО. Если у вас есть два совершенно разных типа с разной валидацией и значением, зачем им использовать один и тот же столбец? Если вы держите пари, что один столбец проще, почему бы просто не использовать SQL_VARIANT, тогда вам не о чем беспокоиться. Вы можете хранить даты, строки и числа, и каждый может устроить большую вечеринку в одной гигантской, бесполезной колонке ...
Аарон Бертран

Откуда поступают IP-адреса? Будут ли они когда-либо включать маску / подсеть (т. Е. 10.10.10.1/124)? Я видел, как это поступает из журналов веб-сервера и не легко переводится в BIGINT (INT не будет работать, так как для вычисления требуется беззнаковый INT, если, конечно, вы не включите эту нормализацию для предположения, что 0 действительно составляет -2,14xxxx миллиарда). Я думаю, маска подсети может быть просто дополнительным полем TINYINT. Но я понимаю, что хочу сохранить как BIGINT, если хочу сопоставить это с БД широты / долготы, чтобы отобразить их. Но, как заметил Аарон, это может быть постоянная вычисляемая цв.
Соломон Руцкий

Ответы:


12

как сохранить фактический IP-адрес - в текстовом или байтовом формате. Что будет лучше?

Поскольку термин «текст» здесь относится к VARCHAR(45)«байты» VARBINARY(16), я бы сказал: ни то, ни другое .

Учитывая следующую информацию (из статьи Википедии по IPv6 ):

Представление адреса
128 бит адреса IPv6 представлены в 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. Вы сэкономите массу пространства на IPv6 - адресов, позволяя либо ROW COMPRESSIONили PAGE COMPRESSION. Поскольку оба типа COMPRESSION позволят использовать поля, которые 0x00должны занимать 0 байтов, все эти группы нулей теперь не будут вам ничего стоить. С другой стороны, если вы сохранили приведенный выше пример адреса (в цитате Википедии), тогда 3 набора всех нулей в середине будут занимать свое полное пространство (если вы не выполняете VARCHAR(45)и не используете сокращенную запись) , но это может не сработать для индексации и потребует специального разбора для восстановления его в полный формат, поэтому давайте предположим, что это не вариант ;-).

Если вам нужно захватить сеть, создайте TINYINTполе для этого с именем [Network]:-)

Для получения дополнительной информации о значении сети, вот некоторая информация из другой статьи Википедии об адресе IPv6 :

сети

В сети IPv6 используется блок адресов, представляющий собой непрерывную группу адресов IPv6, размер которых равен степени двух. Начальный набор битов адресов идентичен для всех хостов в данной сети и называется сетевым адресом или префиксом маршрутизации .

Диапазоны сетевых адресов записаны в нотации 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);

Заметки:

  • Я признаю, что вы планируете использовать BIGINT это поле для идентификатора, но действительно ли вы собираетесь собирать более 4 294 967 295 уникальных значений? Если это так, то просто измените поле на BIGINT, а затем вы даже можете изменить начальное значение на 0. Но в противном случае вам лучше использовать INT и начинать с минимального значения, чтобы вы могли использовать весь диапазон этого типа данных. ,
  • При желании вы можете добавить в эту таблицу один или несколько вычисляемых столбцов NONpersisted для получения текстовых представлений IP-адреса.
  • Поля группы * упорядоченно расположены вниз , от 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закончено, VARBINARYпоскольку DATA_COMPRESSIONнедоступно?
Мэтт

@SolomonRutzky Спасибо за подробное объяснение. Мне интересно, как мне искать между диапазонами адресов? Например, у меня есть поставщик данных, предоставляющий данные IP-геолокации в форме начального и конечного IP-адреса. Мне нужно выяснить, в какой диапазон попадает данный IP-адрес.
J Weezy

@JWeezy Добро пожаловать :). Как хранятся начальный и конечный IP-адреса? Вы используете адреса IPv4 или v6?
Соломон Руцкий

@SolomonRutzky Оба. IPv4 не проблема, потому что я могу хранить его как целое число. К сожалению, в SQL Server нет достаточно большого 128-битного целого или числового типа данных, чтобы справиться с этим. Итак, для IPv6 я сохраняю его в VARBINARY (16), а затем использую оператор BETWEEN для поиска между диапазонами. Но я получаю несколько результатов по диапазонам IP-адресов, которые я не считаю правильными. Я хотел бы использовать один и тот же тип данных для IPv4 и IPv6, если это возможно.
J Weezy

@JWeezy Я собирался предложить BINARY(16);-). Можете ли вы привести пример с начальным / конечным диапазоном и по крайней мере двумя возвращаемыми строками, один действительный и хотя бы один недействительный? Возможно, VARbinary сокращает некоторые значения.
Соломон Руцкий

1

Меньше всегда будет быстрее. С меньшими значениями вы можете разместить больше из них на одной странице, следовательно, меньше ввода-вывода, потенциально меньше B-деревьев и т. Д.

Разумеется, при прочих равных условиях (накладные расходы на перевод, читаемость, совместимость, загрузка процессора, возможность индексации и т. Д.).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.