必须用inet_aton()转整数后再位运算提取网段,如/24用inet_aton(ip) & 0xffffff00;字符串截取不可靠,需正则过滤非法ip,并建议冗余存储网络字段加索引提升性能。

用 INET_ATON() 和位运算提取网段地址
直接对 IP 字符串做 GROUP BY 无法实现网段聚合,必须先将 IP 转为整数、再按子网掩码截取网络地址。MySQL 中最可靠的方式是用 INET_ATON() 转换,配合位运算(&)屏蔽主机位。例如统计 /24 网段:把 IP 转整数后与 0xFFFFFF00(即 255.255.255.0 的整数形式)做与运算。
常见错误是误用字符串截取(如 SUBSTRING_INDEX(ip, '.', 3)),这在 IPv6 或非标准格式(带端口、空格)下直接失效;还有人用 LEFT(ip, LENGTH(ip)-2) 硬切,但遇到 192.168.5.255 这类末段是三位的就会错切。
-
INET_ATON()对非法 IP 返回NULL,需提前用WHERE ip REGEXP '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'过滤 - /24 掩码对应整数
4294967040(即0xFFFFFF00),/28 是4294967280(0xFFFFFFF0),别手算错 - PostgreSQL 用户请改用
inet_network(ip::inet)或(ip::inet - '0.0.0.0/0'::inet)配合masklen
按不同掩码长度分组时如何动态计算网络地址
硬写多个 & 表达式不现实,尤其当需要同时看 /24、/28、/16 流量分布时。推荐用子查询或 CTE 先算出网络地址字段,再外层 GROUP BY。
例如要对比 /24 和 /16 的流量集中度:
SELECT INET_NTOA(INET_ATON(ip) & 0xFFFF0000) AS net16, INET_NTOA(INET_ATON(ip) & 0xFFFFFF00) AS net24, SUM(traffic) AS total FROM log_table WHERE ip REGEXP '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' GROUP BY net16, net24;
注意:MySQL 8.0+ 支持 CTE,可读性更好;5.7 及以下只能嵌套子查询,且 INET_NTOA() 对超大整数可能返回空字符串,建议加 HAVING net24 != '' 安全校验。
性能瓶颈常卡在 INET_ATON() 不走索引
如果表有千万级行,每次 GROUP BY 都实时调用 INET_ATON() 会严重拖慢。根本解法是建生成列(MySQL 5.7+)并加索引:
- 添加持久化列:
ALTER TABLE log_table ADD COLUMN net24 INT UNSIGNED AS (INET_ATON(ip) & 0xFFFFFF00) STORED; - 立刻建索引:
CREATE INDEX idx_net24 ON log_table(net24); - 后续查询直接
GROUP BY net24,速度提升 5–20 倍,取决于数据分布
别试图对原始 ip 字段建普通索引——字符串前缀索引对网段分组完全无效,因为 '192.168.1.' 和 '192.168.10.' 在字典序里根本不相邻。
IPv6 场景下必须换思路
MySQL 的 INET6_ATON() 返回的是 VARBINARY(16),不能直接位运算。正确做法是用 INET6_PREFIX()(MySQL 8.0.28+)或手动截取:
- MySQL 8.0.28+:
INET6_PREFIX(ipv6_addr, 64)直接得 /64 网段二进制值 - 老版本只能转成十六进制字符串,用
SUBSTRING切前 16 位(对应 /64),再拼回::格式,但要注意压缩写法(如::1)需先用INET6_NTOA(INET6_ATON())标准化 - 流量统计时,IPv6 的
net64列建议用BINARY(16)类型存储,避免字符串比较开销
真正麻烦的不是语法,而是业务日志里 IPv4 和 IPv6 混存——这时候得先用 IS_IPV4()/IS_IPV6() 分流处理,强行统一格式只会引入隐性错误。











