unsigned int 是 mysql 5.7 中存储 ipv4 地址最高效的选择,仅占 4 字节,支持 inet_aton()/inet_ntoa() 转换,且便于索引、范围查询和排序;varchar(15) 实际至少占 8 字节,字符串比较慢,无法高效支持网段查询。

用 UNSIGNED INT 存 IPv4,别用 VARCHAR(15)
直接结论:在 MySQL 5.7 中,UNSIGNED INT 是存储 IPv4 地址最高效的选择。它占 4 字节,而 VARCHAR(15) 实际最少占 8 字节(15 字符 + 1 字节长度标识 + 行格式开销),且字符串比较比整数比较慢得多。
常见错误是看到 IP 长得像字符串,就直觉建 VARCHAR —— 这会让索引变大、缓存效率下降、范围查询(比如查某网段)变慢。MySQL 5.7 完全支持 INET_ATON() 和 INET_NTOA(),无需额外依赖。
-
INET_ATON('192.168.1.1')返回3232235777,可直接插入UNSIGNED INT字段 -
INET_NTOA(3232235777)返回'192.168.1.1',适合 SELECT 展示 - 注意:
INET_ATON()对非法格式(如'192.168.01.1'带前导零)返回NULL,应用层需校验或清洗
建表时必须加 UNSIGNED,否则会溢出
IPv4 最大值是 255.255.255.255 → 4294967295,而有符号 INT 最大只能到 2147483647。一旦存入 128.0.0.1 以上地址,就会被截断或报错。
正确写法:
CREATE TABLE access_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, ip_addr INT UNSIGNED NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );
- 别漏掉
UNSIGNED—— 这是 MySQL 5.7 中最容易忽略也最致命的细节 - 如果字段已有数据但类型是普通
INT,用ALTER TABLE ... MODIFY ip_addr INT UNSIGNED转换,但要确认存量值没超限 -
INET_ATON()返回的是无符号整数,若目标列不是UNSIGNED,MySQL 5.7 默认会静默转成有符号值,导致高位为负(比如INET_ATON('192.168.1.1')存成-1062731519)
查询时避免在 WHERE 中用 INET_NTOA()
想查“所有来自 192.168.x.x 的请求”,别这么写:
SELECT * FROM access_log WHERE INET_NTOA(ip_addr) LIKE '192.168.%';
这会让 ip_addr 列无法走索引,全表扫描不可避免。正确做法是把条件转成数值范围:
SELECT * FROM access_log
WHERE ip_addr BETWEEN INET_ATON('192.168.0.0') AND INET_ATON('192.168.255.255');
- MySQL 5.7 不支持函数索引,所以
WHERE INET_NTOA(...)永远无法利用索引 - 子网掩码计算建议在应用层做(比如 Python 用
ipaddress.ip_network('192.168.0.0/16').network_address),再传两个整数给 SQL - 如果真要模糊匹配(如查某 ISP 段),提前算好起止值并缓存,别每次 SELECT 都调
INET_ATON()
兼容性提醒:IPv6 不能用 INET_ATON()
MySQL 5.7 的 INET_ATON() 和 INET_NTOA() 仅支持 IPv4。遇到 IPv6 必须换方案:
- 升级到 MySQL 8.0+ 才能用
INET6_ATON()/INET6_NTOA() - MySQL 5.7 下只能退回到
VARCHAR(39)(IPv6 最长压缩后是 39 字符),或用BINARY(16)自行实现转换逻辑 - 如果业务已混用 IPv4/IPv6,建议单拆字段:
ip_v4 INT UNSIGNED NULL+ip_v6 VARBINARY(16) NULL,查的时候判非空
真正麻烦的不是存,而是查——一旦表里 IPv4 和 IPv6 共存,几乎所有范围类、聚合类查询都得拆逻辑处理,5.7 没有原生抽象能力,这点容易低估。











