Зберігання 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.