正确做法是将ip转为整数后用位运算提取网段前缀,如inet_aton(ip) & inet_aton('255.255.255.0');长期使用需预计算字段加索引,避免每次运算性能差和边界遗漏。

用 INET_ATON() 和位运算提取网段前缀
直接对 IP 字符串按点分十进制截取(比如 SUBSTRING_INDEX(ip, '.', 3))看似简单,但会出错:IPv4 地址不是所有都严格四段,10.1.1.1 和 10.1.1.128 在 /24 网段下应归为同一组,但字符串截断无法表达“掩码长度”语义,更无法兼容 CIDR 表示法。正确做法是转为整数后用位运算对齐前缀。
关键操作是:INET_ATON(ip) & INET_ATON('255.255.255.0')(对应 /24),或通用写法:INET_ATON(ip) & ~((1 。MySQL 不支持动态掩码位数计算,所以实际中常把掩码写死或用 CASE 分情况。
- 确保字段类型是
VARCHAR或TEXT,INET_ATON()对非法 IP(如'256.1.1.1')返回NULL,需提前清洗或加WHERE ip REGEXP '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' -
INET_ATON()在 MySQL 8.0+ 支持 IPv6 的INET6_ATON(),但位运算逻辑不直接适用,IPv6 建议用LEFT(INET6_NTOA(ipv6_bin), 9)这类近似截断(仅限固定前缀场景) - 结果仍是整数,聚合时建议用
INET_NTOA()转回可读格式,例如:SELECT INET_NTOA(net_prefix) AS network, COUNT(*) FROM (...) GROUP BY net_prefix
按常见 CIDR 掩码(/24、/16、/8)分别处理
生产中多数统计只需几个固定网段粒度,硬编码比动态计算更稳、更易读、也避免函数嵌套导致索引失效。别试图写一个“自动适配任意 mask”的通用函数——MySQL 没有原生 CIDR 解析函数,强行抽象反而难维护。
- /24(C类):
INET_NTOA(INET_ATON(ip) & 0xFFFFFF00)(等价于INET_ATON('255.255.255.0')) - /16(B类):
INET_NTOA(INET_ATON(ip) & 0xFFFF0000) - /8(A类):
INET_NTOA(INET_ATON(ip) & 0xFF000000) - 注意:十六进制掩码必须带
0x前缀,否则会被当十进制数解析,255.255.255.0直接写会报错
性能与索引注意事项
用 INET_ATON() 包裹字段后无法走索引,WHERE INET_ATON(ip) & 0xFFFF0000 = 0xC0A80000 这种条件会让全表扫描。真要高频查某网段,得冗余存一个 network_24 字段,写入时就计算好并建索引。
- 临时分析可用派生表或 CTE 预计算前缀列,例如:
WITH ip_net AS (SELECT ip, INET_NTOA(INET_ATON(ip) & 0xFFFFFF00) AS net24 FROM log_table) SELECT net24, COUNT(*) FROM ip_net GROUP BY net24 - 如果表超千万行且常按网段查,务必在写入侧加触发器或应用层补全
ip_network_24列,并对其建 B-tree 索引 -
INET_ATON()在低版本 MySQL(如 5.6)对空字符串或 NULL 返回 0,可能造成误聚合,务必加WHERE ip IS NOT NULL AND ip != ''
遇到 NULL 或聚合结果异常怎么办
最常见原因是数据含非标准 IP,比如带端口的 '192.168.1.1:8080'、内网保留地址('0.0.0.0')、或 IPv6 混入 IPv4 字段。这些都会让 INET_ATON() 返回 NULL,进而导致整个分组消失(GROUP BY NULL 不成立)。
- 先跑一遍
SELECT ip, INET_ATON(ip) FROM t WHERE INET_ATON(ip) IS NULL LIMIT 10定位脏数据 - 用正则过滤再转换:
WHERE ip REGEXP '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' - 聚合时加
HAVING COUNT(*) > 0无意义,真正要的是排除NULL前缀:GROUP BY net24 HAVING net24 IS NOT NULL
实际做网段聚合时,核心不是“怎么写 SQL”,而是想清楚:这个统计是临时看一眼,还是会长期用于告警或报表?前者用带正则校验的 INET_ATON + 位运算 快速出数;后者必须改表结构,加预计算字段和索引——不然每次跑都慢,还容易漏掉边界 case。











