Optimización del Almacenamiento de Direcciones IP en Bases de Datos

Aprende por qué INT UNSIGNED es mejor que VARCHAR para almacenar direcciones IP en bases de datos y cómo implementarlo correctamente.

El problema con VARCHAR para IPs

Por inercia, muchos desarrolladores almacenan direcciones IP como cadenas VARCHAR(15) en sus bases de datos. Si bien esto funciona, tiene varias desventajas:

  • Mayor consumo de espacio (hasta 15 bytes por dirección)
  • Búsquedas menos eficientes
  • Validación adicional requerida
  • Problemas de ordenamiento incorrecto

💡 ¿Sabías que?

Una dirección IPv4 realmente es un número de 32 bits, que puede ser almacenado en solo 4 bytes usando INT UNSIGNED, en lugar de los 15 bytes que requiere VARCHAR.

Solución: INT UNSIGNED y funciones MySQL

MySQL ofrece funciones nativas para convertir entre la representación de cadena y numérica de direcciones IP:

Conversión de IP a entero

-- Convertir una dirección IP a formato numérico
SELECT INET_ATON('192.168.0.10') AS ip_numero;
-- Resultado: 3232235530

Conversión de entero a IP

-- Convertir un número de vuelta a dirección IP
SELECT INET_NTOA(3232235530) AS ip_direccion;
-- Resultado: '192.168.0.10'

Implementación en una tabla

Creación de una tabla optimizada para almacenar direcciones IP:

CREATE TABLE acceso_usuarios (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    usuario_id INT UNSIGNED NOT NULL,
    -- En lugar de: ip_address VARCHAR(15)
    ip_address INT UNSIGNED NOT NULL,
    fecha_acceso TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_ip (ip_address)
);

Inserción de datos

-- Insertar una nueva dirección IP convertida a entero
INSERT INTO acceso_usuarios (usuario_id, ip_address)
VALUES (1025, INET_ATON('192.168.0.10'));

Consulta de datos

-- Consultar la dirección IP en formato legible
SELECT id, usuario_id, INET_NTOA(ip_address) AS ip, fecha_acceso
FROM acceso_usuarios;

Comparación: VARCHAR vs INT UNSIGNED

VARCHAR(15)
  • 15 bytes por dirección
  • Búsquedas más lentas
  • Ordenamiento lexicográfico
  • Validación manual necesaria
  • Problemas con espacios en blanco
INT UNSIGNED
  • Solo 4 bytes por dirección
  • Búsquedas más rápidas
  • Ordenamiento numérico correcto
  • Validación automática con INET_ATON
  • Formato consistente

Ejemplo de ordenamiento incorrecto con VARCHAR

Al ordenar direcciones IP como cadenas, el resultado no es el esperado:

IP como VARCHAROrden lexicográficoOrden numérico correcto
192.168.0.1
192.168.0.2
192.168.0.10
192.168.0.20

Consideraciones para IPv6

Para direcciones IPv6, que son de 128 bits, MySQL ofrece funciones similares:

-- Para IPv6 (MySQL 5.6 y superior)
SELECT INET6_ATON('2001:0db8:85a3:0000:0000:8a2e:0370:7334');
SELECT INET6_NTOA(direccion_ipv6) FROM tabla;

Para almacenar IPv6, se recomienda usar BINARY(16) en lugar de INT UNSIGNED:

CREATE TABLE conexiones (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    -- Para IPv4 e IPv6
    ip_address VARBINARY(16) NOT NULL,
    es_ipv6 BOOLEAN DEFAULT FALSE
);

Mejores Prácticas

En aplicaciones

En tu código de aplicación, puedes realizar las conversiones:

Ejemplo en PHP:

// Convertir IP a entero para almacenar
$ip_int = ip2long('192.168.0.10');
// Convertir de vuelta para mostrar
$ip_str = long2ip($ip_int);

Ejemplo en Python:

# Convertir IP a entero
import socket, struct
ip_int = struct.unpack("!L", socket.inet_aton(ip_str))[0]
# Convertir de vuelta
ip_str = socket.inet_ntoa(struct.pack("!L", ip_int))

En consultas

Para búsquedas por rango de IPs:

-- Encontrar todas las IPs en el rango 192.168.0.0 - 192.168.0.255
SELECT * FROM acceso_usuarios
WHERE ip_address BETWEEN 
    INET_ATON('192.168.0.0') AND INET_ATON('192.168.0

Comentarios

Publicar un comentario

Popular Posts