Как хранить IPv4 в БД

10 комментариев

Как ни странно, но многие разработчики до сих пор хранят ip адрес в базе плейн текстом, что вобщем то совсем непрактично.

На самом деле адрес IPv4 удобно хранить в виде беззнакового int 32. И в этой кратенькой статье я вам расскажу о том, как это делать и какие неоспоримые преимущества это дает. Я думаю, этот материал будет полезен многим.


UPD: Написал схожую статью для IPv6, так же советую прочесть: Работа c IPv6 в PHP и MySQL

Работа в PHP

Для начала рассмотрим способ преобразования ip в int. В PHP есть функция ip2long:

ip2long — Конвертирует строку, содержащую (IPv4) Интернет адрес с точками в допустимый адрес

Возвращает IPv4 адрес или FALSE, если параметр ip_address содержит ошибку.

То есть возвращает unsigned 32 int. Это то что нам подходит. Но появляется вот какой нюанс, в зависимости от того какой разрядности у нас система, эта функция будет выдавать разные результаты. Вот, к примеру, на 64 битной:

ip2long(0.0.0.0);          //0
ip2long(127.255.255.255); //2147483647
ip2long(128.0.0.0);        //2147483648
ip2long(255.255.255.255); //4294967295

Собственно, все верно. Так и должно быть. Но вот на 32 разрядной системе это будет выглядеть иначе:

ip2long(0.0.0.0);          //0
ip2long(127.255.255.255); //2147483647
ip2long(128.0.0.0);        //-2147483648
ip2long(255.255.255.255); //-1

Этот момент, кстати, отмечен в документации:

Ввиду того, что PHP тип integer является знаковым, и на 32-битных системах большое количество IP адресов будет представлено в виде отрицательных чисел, необходимо использовать «%u» в функции sprintf() или printf() для получения IP адреса в строковом беззнаковом виде.

Посему, эту функцию, при работе с базой данных, следует использовать так:

$ip = sprintf('%u', ip2long($ip));

Кстати о базе данных. Для поля в котором вы будете хранить IP адрес, не забывайте указывать UNSIGNED, иначе все ваши труды по правильному представлению числа в самом PHP пойдут насмарку.

Обратное преобразование выполняется с помощью функции long2ip. С ней, я думаю, вы сможете разобраться самостоятельно.

Работа в SQL

В MySQL есть две встроенные функции

INET_ATON(expr) — Given the dotted-quad representation of an IPv4 network address as a string, returns an integer that represents the numeric value of the address in network byte order (big endian). INET_ATON() returns NULL if it does not understand its argument.

INET_NTOA(expr) — Given a numeric IPv4 network address in network byte order, returns the dotted-quad representation of the address as a binary string. INET_NTOA() returns NULL if it does not understand its argument.

Запоминаются просто — INET_ATONINET_Adress_TO_Number — Адрес_В_Число, и INET_NTOAINET_Number_TO_Adress — Число_В_Адрес.

То есть можно не заморачиваться с приведением адреса IP в скриптах, а возложить эту обязанность на БД. Так например:
INSERT .... VALUES(..., INET_ATON('255.255.255.255'), ...)
SELECT .... WHERE ip = INET_ATON('255.255.255.255')

Но самая крутая возможность — это операции поиска по диапазонам и маскам.

Задача: нам нужно выдернуть все записи находящиеся в диапазоне 158.192.12.0 — 158.192.74.255. Если бы вы хранили IP плейнтекстом, то вам бы пришлось реализовывать это с помощью регулярных выражений, и подобный запрос выполнялся бы очень долго.

Но в нашем случае, все очень просто:
SELECT .... WHERE ip BETWEEN INET_ATON('158.192.12.0') AND INET_ATON('158.192.94.255')
И если вы используете индекс по данному полю, то результат вернется практически моментально.

Или надо получить адрес подсети по маске:
SELECT ..., (ip & INET_ATON('255.255.255.0')), ...

Вобщем все что вашей душе угодно :)

Подведем итог

Чем же все таки этот метод лучше:

  • в базе данных теперь будет хранится 4 байта INT вместо 15 байт VARCHAR;
  • бОльшая скорость при использовании операций выборки по ip адресу;
  • простой поиск по диапазону;
  • битовые операции с IP адресами.

Минусы:

  • перевод формы представления адреса из строки в число и обратно;
  • отсутствие человеко-понятного отображения значения при просмотре значений через, к примеру, phpMyAdmin.

Кстати в phpMyAdmin, по крайней мере в последних версиях, есть возможность отображать такие значения в понятном виде автоматически (см. здесь).

      1. Дмитрий Амиров Автор

        Да, не запишешь. Кстати сказать, в ближайшее время планирую написать статью по поводу работы с IPV6 средствами PHP.

  1. Окта

    В любом случаее надо соблаюдать тип полей при вставке IP в базу: либo INT (для +/-), либо UNSIGNED INT (только +).
    А еще можно переложить операции по переводу адресов на сам SQL.

  2. Dan

    Если я собираюсь использовать INET_ATON и INET_NTOA — какое поле мне надо создать в MySQL (int, long?), как его настроить, какой размер задать?

Добавить комментарий

Прочли запись? Понравилась? Не стесняйтесь, оставьте, пожалуйста, свой комментарий. Мне очень интересно, что вы думаете об этом. Кстати в комментарии вы можете задать мне любой вопрос. Я обязательно отвечу.

Вы можете оставить коментарий анонимно, для этого можно не указывать Имя и email. Все комментарии проходят модерацию, поэтому ваш комментарий появится не сразу.