MySQL 8.0.19+ 推荐用 VARBINARY(16) 配合 INET6_ATON()/INET6_NTOA() 存储 IPv4/IPv6,因统一二进制格式、节省空间、支持高效索引与范围查询;VARCHAR 存 IP 易导致字符串比较失效、无法 CIDR 匹配、索引低效且同地址多写法被视为不同值。

直接用 INET_ATON() 存 IPv4、用 INET6_ATON() 存 IPv6,配合 VARBINARY(16) 字段,是当前最平衡的方案——既兼容双栈,又支持索引和范围查询,还不依赖 MySQL 版本降级或额外字段拆分。
为什么不能只用 VARCHAR 存 IP?
看似省事,但实际埋坑:IPv6 地址最长可达 39 字符(如 2001:0db8:0000:0000:0000:ff00:0042:8329),VARCHAR(45) 虽能兜住,但字符串比较无法做 CIDR 匹配、子网判断或高效范围扫描;同一地址不同写法(比如 ::1 和 0000:0000:0000:0000:0000:0000:0000:0001)会被当成不同值;加索引后仍无法利用 B+Tree 的数值有序性加速 BETWEEN 查询。
VARBINARY(16) 是怎么同时装下 IPv4 和 IPv6 的?
IPv4 地址本质是 4 字节,IPv6 是 16 字节,VARBINARY(16) 刚好容纳两者——MySQL 的 INET6_ATON() 会把 IPv4 自动“映射”为 IPv6 格式(即 ::ffff:192.168.1.1 → 00000000000000000000FFFFC0A80101),统一转成 16 字节二进制存入。这意味着:
-
INET6_ATON("127.0.0.1")返回的是 16 字节值,前 12 字节为 0,后 4 字节是INET_ATON()结果 - 查 IPv4 时可用
WHERE ip_address = INET6_ATON("192.168.1.1"),查 IPv6 同理,无需类型判断 - 字段加普通 B-Tree 索引即可支持等值、范围、
LIKE 'xxx%'(不推荐)等查询 - 注意:MySQL 5.6.3+ 支持
INET6_ATON(),但 5.6.3–5.7.7 有 bug,返回 NULL 时可能不报错,建议最低用 5.7.8+
INET_ATON() 和 INET6_ATON() 混用会出什么问题?
常见错误是给 IPv6 用 INET_ATON():它只认点分十进制,遇到冒号就返回 NULL,而 INSERT 时若字段非空且没设默认值,会直接报错 Column 'ip_address' cannot be null;反过来,用 INET6_ATON() 存 IPv4 没问题,但读取时必须用 INET6_NTOA(),不能用 INET_NTOA()(后者对 IPv6 输入返回 NULL)。
实操要点:
- 插入统一走
INET6_ATON(),不管输入是 IPv4 还是 IPv6 字符串 - 查询展示统一用
INET6_NTOA(ip_address),它能正确还原两种格式 - 避免在 WHERE 中嵌套函数,例如
WHERE INET6_NTOA(ip_address) = "..."会导致索引失效 - 如果业务强依赖 IPv4 数值计算(如掩码 & 运算),可额外保留一个
INT UNSIGNED字段专存 IPv4 的INET_ATON()值,但不要主键/索引双写
性能和兼容性容易被忽略的细节
真正上线时卡住的往往不是设计,而是这些点:
-
VARBINARY(16)字段本身不自动校验合法性——INET6_ATON("hello")返回NULL,插入后该行 IP 字段就是 NULL,应用层需提前校验或加 CHECK 约束(MySQL 8.0.16+ 支持) - 排序行为:按
VARBINARY二进制值排,IPv4 映射后的值恒小于纯 IPv6,所以ORDER BY ip_address是有意义的,但语义上不是“IP 数值升序”,而是“网络字节序升序” - 连接池或 ORM(如 SQLAlchemy、MyBatis)可能对
VARBINARY返回 bytes 对象而非 hex 字符串,应用层解码逻辑要适配INET6_NTOA()输出格式 - 备份恢复时,
mysqldump默认导出二进制字段为十六进制字面量(如0x00000000000000000000FFFFC0A80101),没问题;但用LOAD DATA INFILE导入文本 CSV 时,必须确保 IP 列是字符串格式,由 SQL 函数转换


















