今天我们来聊一个在数据库中存储 IP 地址的常见问题:如果面试官问你“如果要存 IP 地址,用什么数据类型比较好?”,你该如何回答呢?
这个问题看似简单,但其实涉及到了存储空间、查询效率和数据处理方式等多个方面。

1. 引言

在很多系统中,尤其是跟网络相关的应用,我们都需要存储 IP 地址。你可能会想,存个 IP 地址又能有啥难度?不过,存储的方式却大有讲究。比如,最常见的存储方式是使用字符串(VARCHAR 类型),但我给你推荐一个更加高效的方式——使用无符号整数(UNSIGNED INT)来存储 IPv4 地址。
听起来是不是有点意外?别急,接下来我会从存储空间、查询效率等方面来分析这个选择的优势。

2. 使用无符号整数的优点

节省存储空间

我们都知道,存储空间是有限的,尤其是在需要存储大量数据时。IPv4 地址由四个字节(32 位)组成,每个字节表示一个 0 到 255 之间的数字。如果你用 VARCHAR(15) 来存储一个 IP 地址,实际上你每个 IP 地址都会占用 15 个字符的空间。由于每个字符通常需要 1 字节,所以存储一个 IPv4 地址的空间是 15 字节。
而如果使用无符号整数(UNSIGNED INT)来存储 IPv4 地址,只需要 4 字节。为什么只需要 4 字节?因为无符号整数最大可以表示 0 到 4294967295 之间的数字,而这恰好足以表示所有 IPv4 地址。因此,存储一个 IPv4 地址只需要 4 字节,相比字符串的 15 字节,节省了大量的空间。

节省索引空间

假设你有一个数据库表,里面有上千万的记录,存储着 IP 地址。如果你选择了 VARCHAR(15) 类型,那么每个 IP 地址会占用更多的存储空间,同时索引也会占用更多空间,查询效率自然就会受到影响。另一方面,如果你使用 UNSIGNED INT 存储,索引会变得更小、更高效,查询速度也会显著提高。

提高查询效率

无符号整数不仅节省存储空间,还能显著提高查询效率。特别是在进行范围查询时,比如你需要查询某个 IP 地址段内的所有记录,使用无符号整数会比字符串更高效。因为在进行比较操作时,数字比较要比字符串比较更快。

3. 存储方式对比

我们来看看存储 IPv4 地址的两种常见方式——字符串和无符号整数。

字符串存储:VARCHAR(15)

CREATE TABLE ip_addresses (
ip VARCHAR(15)
);
VARCHAR(15) 适用于存储人类可读的 IP 地址,例如 192.168.1.1。但是,由于每个 IP 地址占用 15 个字符(包括点),这种方式的存储和索引开销会比较大。

无符号整数存储:UNSIGNED INT

CREATE TABLE ip_addresses (
ip UNSIGNED INT
);
UNSIGNED INT 类型只需要 4 字节,显著节省了存储空间。使用这种方式存储 IP 地址时,你可以将 IP 地址转换为一个 32 位的无符号整数。

4. 存储方式的缺点

当然,使用无符号整数存储 IP 地址也不是没有缺点。最大的缺点是它不直观。作为程序员,我们习惯于看到类似 192.168.1.1 这样的地址,而不是一个看起来毫无意义的数字 3232235777。这种数字对于人来说非常难以理解和调试。
另外,使用无符号整数存储 IP 地址时,还需要做格式转换。你不能直接将 192.168.1.1 插入到数据库中,而是要通过一个转换函数将其转换为整数格式,然后再存储。

5. MySQL 转换函数

在 MySQL 中,我们可以使用 INET_ATON() 和 INET_NTOA() 函数来进行 IP 地址和无符号整数之间的转换。

INET_ATON():将字符串 IP 转换为整数

SELECT INET_ATON(‘192.168.1.1’);
这条语句会将 IP 地址 192.168.1.1 转换为一个无符号整数,返回结果是 3232235777

INET_NTOA():将整数 IP 转换为字符串

SELECT INET_NTOA(3232235777);
这条语句会将无符号整数 3232235777 转换回 192.168.1.1

6. IPv6 存储

IPv6 地址的长度比 IPv4 长得多,它由 128 位组成,因此我们无法使用 UNSIGNED INT 来存储 IPv6 地址。此时,最合适的存储方式是使用 VARBINARY(16) 类型。VARBINARY(16) 可以存储一个 128 位的二进制数。你可以使用相应的转换函数(如 INET6_ATON 和 INET6_NTOA)来进行格式转换。
CREATE TABLE ipv6_addresses (
ip VARBINARY(16)
);

7. Java 示例

在 Java 中,我们同样可以将 IP 地址转换为 long 类型(对应无符号整数),然后进行存储。

ip2Long:将字符串 IP 转换为 long 类型

public class IPConversion {
public static long ipToLong(String ip) {
String[] ipParts = ip.split(“\\.”);
long result = 0;
for (int i = 0; i < ipParts.length; i++) {
result |= (Long.parseLong(ipParts[i]) << (24 – (8 * i)));
}
return result;
}

public static void main(String[] args) {
String ip = “192.168.1.1”;
System.out.println(ipToLong(ip));  // 输出 3232235777
}
}

long2Ip:将 long 类型的 IP 转换为字符串

public class IPConversion {
public static String longToIp(long ip) {
return (ip >> 24) + “.” + ((ip >> 16) & 255) + “.” + ((ip >> 8) & 255) + “.” + (ip & 255);
}

public static void main(String[] args) {
long ip = 3232235777L;
System.out.println(longToIp(ip));  // 输出 192.168.1.1
}
}

8. 总结

所以,存储 IP 地址的最佳方式是什么呢?如果你是在处理 IPv4 地址,我强烈推荐使用无符号整数(UNSIGNED INT)来存储。它不仅节省了存储空间,还能提高查询效率,尤其在大量数据的场景下。
虽然你需要在存储和展示时进行格式转换,但在效率上你会得到很大的提升。而对于 IPv6 地址,则应该选择 VARBINARY 来存储。
希望这篇文章能给你一些启发,让你在面对面试官的提问时,不仅能给出正确的答案,还能展示出你对数据存储和性能优化的深刻理解。
扫码领红包

微信赞赏支付宝扫码领红包

发表回复

后才能评论