Зберігання IP-адрес - varchar (45) vs 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поля, і я вважаю за краще зберігати значення в одному полі - мені здається більш природним.
— gotqn

1
Ще не розумію, чому INT замість 4-х TINYINT? Те саме сховище, простіша налагодження, менше дурниць, IMHO. Якщо у вас є два абсолютно різних типи з різною валідацією та значенням, чому їм потрібно використовувати один і той же стовпець? Якщо ви робите ставку на те, що один стовпчик простіший, чому б просто не використовувати SQL_VARIANT, то вам нічого не потрібно турбуватися. Ви можете зберігати дати та рядки та цифри, і кожен може влаштувати велику вечірку в одній гігантській, непотрібній колонці ...
— Аарон Бертран

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

Відповіді:


13

як зберігати фактичну IP-адресу - у текстовому чи байтовому форматі. Що буде краще?

Оскільки тут "текст" посилається на VARCHAR(45)"байт" VARBINARY(16), я б сказав: жодне .

З огляду на таку інформацію (зі статті Вікіпедії на IPv6 ):

Представлення адреси
128 біт адреси IPv6 представлені у 8 групах по 16 біт кожна. Кожна група записується у вигляді чотирьох шістнадцяткових цифр, а групи розділені двокрапками (:). Адреса 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. Оскільки обидва типи КОМПРЕСІЇ дозволять використовувати поля, які мають 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 і починати з мінімального значення, щоб ви могли використовувати весь діапазон цього типу даних .
  • За бажанням ви можете додати в цю таблицю один або кілька обчислених стовпців, що не підтримуються, щоб повернути текстові подання IP-адреси.
  • Поля Group * розташовані цілеспрямовано вниз , з 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-сервері не існує 128 типів даних, пов’язаних з цілими числами чи числами, достатньо великих, щоб обробляти їх. Отже, для IPv6 я зберігаю його у VARBINARY (16), а потім використовую оператор BETWEEN для пошуку між діапазонами. Але я отримую кілька результатів на діапазонах IP, що я не вважаю правильним. Я хотів би використовувати один і той же тип даних для IPv4 і IPv6, якщо це можливо.
— J Weezy

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

1

Менший завжди буде швидше. З меншими значеннями ви можете розмістити їх більше на одній сторінці, тому менше IO, потенційно дрібніші B-Дерева тощо.

Зрозуміло, що всі інші речі (накладні витрати на переклад, читабельність, сумісність, завантаження процесора, зручність використання індексу тощо).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.