项目上现在需要存储 IP 地址,数据库应该用什么类型来存储?
- 标签:后端、场景题
🏛 业务架构师
存储 IP 地址,我的标准答案是用无符号整型 INT UNSIGNED(4 字节)存 IPv4,配合 MySQL 的转换函数,而不是直觉上的字符串。
推荐做法
sql
ip_start INT UNSIGNED NOT NULL,
-- 写入
INSERT INTO t(ip) VALUES (INET_ATON('192.168.1.100'));
-- 读取
SELECT INET_NTOA(ip) FROM t;为什么不用 VARCHAR(15)
- 空间:15 字节 vs 4 字节,省 70%+,IP 字段通常出现在亿级日志表里,这是实打实的存储成本
- 查询性能:字符串比较是逐字符的且受字典序影响(
'9.x' > '10.x'的坑),整数比较走索引又快又正确 - 范围查询:封禁网段(
192.168.0.0/16)用整数区间BETWEEN一次索引命中,字符串要写前缀 LIKE,索引直接失效
IPv6 的处理:地址 128 位,INT 装不下,用 VARBINARY(16) + INET6_ATON/NTOA,MySQL 5.6+ 原生支持。新系统我会直接按 IPv6 方案建表——国内 IPv6 普及率已过半, retrospective 改造表结构的成本远高于建表时多留 12 字节。
业务场景提醒:IP 在业务里主要用于风控(异地登录识别、黑名单封禁)和审计溯源,两个场景都依赖高频等值/区间查询,整数化是查询成本和风控响应速度的基础。另外要提醒团队:IP 存储属于个人信息,合规上要有脱敏策略和留存期限设计,技术选型时一并落到方案里。
🏗 系统架构师
我会从存储效率、索引效率、IPv6 兼容三个维度选型,先给结论再讲依据。
方案对比
VARCHAR(15):可读性最好,但最多 15 字节(IPv4),索引空间大、按 IP 范围查询(BETWEEN '10.0.0.1' AND '10.0.0.255')语义不可靠——字符串比较和数值比较在进位处不一致,这是隐蔽的正确性坑。INT UNSIGNED(4 字节):IPv4 的天然表示,用INET_ATON()/INET_NTOA()转换,等值和范围查询都走数值语义,索引最小最快。纯 IPv4 场景我的首选。VARBINARY(16)/BINARY(16):INET6_ATON()直接支持 IPv6(16 字节),MySQL 5.6+ 推荐,是新系统的默认选择——2026 年 IPv6 流量占比过半,新表设计必须向后兼容。
sql
CREATE TABLE t (
ip VARBINARY(16) NOT NULL,
KEY idx_ip (ip)
);
INSERT INTO t VALUES (INET6_ATON('2001:db8::1'));
SELECT INET6_NTOA(ip) FROM t
WHERE ip BETWEEN INET6_ATON('10.0.0.0') AND INET6_ATON('10.0.0.255');量级视角 10 亿级访问日志表,VARCHAR(15) 平均 12 字节 + 变长开销 vs INT 4 字节,索引和缓冲池占用差 3 倍,直接决定内存里能缓存多少热数据。转换函数的计算开销是纳秒级,可以忽略。
架构延伸:查 IP 归属地这类需求不落在 MySQL,而是加载 IP 段库(离线导入的有序区间表或内存二分),DB 只做精确点查;海量 IP 分析走 ClickHouse 的 IPv4/IPv6 原生类型,那又是另一套选型。
💻 全栈开发专家
我统一用 INT UNSIGNED 存 IPv4(4 字节),配合 MySQL 内置函数转换,IPv6 用 VARBINARY(16)。
方案
sql
CREATE TABLE t_ip (
ip INT UNSIGNED NOT NULL,
...
) ;
-- 写入
INSERT INTO t_ip(ip) VALUES (INET_ATON('192.168.1.100'));
-- 读取
SELECT INET_NTOA(ip) FROM t_ip;
-- 范围查询(网段)依然可走索引
SELECT * FROM t_ip WHERE ip BETWEEN INET_ATON('192.168.0.0') AND INET_ATON('192.168.0.255');若要同时兼容 IPv4/IPv6,直接 VARBINARY(16) + INET6_ATON()/INET6_NTOA()(MySQL 8 起原生支持)。
为什么不用 VARCHAR(15)
- 15 字节 vs 4 字节,索引体积差近 4 倍。
- 字符串比较是逐字节的,
'9.x' > '10.x'这种字典序和数值序不一致,范围统计会出错,还依赖格式统一(多写少个零就完蛋)。 INT UNSIGNED可比较、可聚合(转网段、算归属地二分查找都方便)。
踩过的坑
- Java 侧
INET_ATON结果要用long接(无符号 int 超出 int 正数范围会被标成负数),MyBatis 映射成Long再转。 - 归属地查询量大时,我会把 IP 段库预载进内存或 Redis 二分查找,不实时查库。