必须转成整数,因IP字符串字典序与数值大小不一致,且主流SQL引擎除PostgreSQL inet外均不原生支持CIDR;IPv4是32位无符号整数,按a×256³+b×256²+c×256+d计算,各库可用标准字符串函数手算,结果须用UNSIGNED INT或BIGINT存储。

直接把 IP 地址转成整数,再用 BETWEEN 或 CASE WHEN 做区间判断,是最稳、最通用的做法。浮点或字符串比较都不可靠,IPv4 必须走整型转换。
为什么必须转成整数?
IP 地址不是自然排序的字符串:比如 "192.168.2.1" 字典序大于 "192.168.10.1",但实际数值更小;MySQL、PostgreSQL、SQL Server 都不原生支持 CIDR 区间分组(除 PostgreSQL 的 inet 类型外),硬匹配子网掩码成本高、可读性差。
关键点:
- IPv4 是 32 位无符号整数,
"a.b.c.d"→a*256³ + b*256² + c*256 + d - 所有主流 SQL 引擎都支持该计算,无需扩展函数
- 避免用
INET_ATON()(MySQL 专用)、TO_NUMBER()拆字段等低效写法
MySQL / PostgreSQL / SQL Server 通用转换写法
不用依赖内置函数,手算更可控。以 MySQL 为例(其他引擎仅需微调类型或除法语法):
SELECT ip_str, (SUBSTRING_INDEX(ip_str, '.', 1) * 16777216 + SUBSTRING_INDEX(SUBSTRING_INDEX(ip_str, '.', 2), '.', -1) * 65536 + SUBSTRING_INDEX(SUBSTRING_INDEX(ip_str, '.', 3), '.', -1) * 256 + SUBSTRING_INDEX(ip_str, '.', -1)) AS ip_int FROM access_log;
说明:
-
SUBSTRING_INDEX(ip_str, '.', 1)提取第一段(a),乘256³ = 16777216 -
SUBSTRING_INDEX(SUBSTRING_INDEX(...), '.', -1)是安全取第 n 段的标准写法,防空段 - PostgreSQL 可用
SPLIT_PART(ip_str, '.', n)::int,SQL Server 用PARSENAME(REPLACE(ip_str, '.', ' '), n) - 注意:结果要用
UNSIGNED INT或BIGINT存储,避免负数溢出(如 MySQL 中INT有符号会截断)
按预设 IP 段做分组归类(CASE WHEN + BETWEEN)
建一张 ip_range_map 表存区间和分类标签,比硬编码 CASE 更易维护:
CREATE TABLE ip_range_map (
range_name VARCHAR(50),
start_ip BIGINT UNSIGNED,
end_ip BIGINT UNSIGNED
);
INSERT INTO ip_range_map VALUES
('内网-办公区', CONV('10.0.0.0', 10, 10), CONV('10.255.255.255', 10, 10)),
('内网-测试区', CONV('172.16.0.0', 10, 10), CONV('172.31.255.255', 10, 10));关联查询时用 BETWEEN 判断:
SELECT l.ip_str, COALESCE(m.range_name, '未知') AS region FROM access_log l LEFT JOIN ip_range_map m ON (l.ip_int BETWEEN m.start_ip AND m.end_ip);
注意点:
- 务必给
ip_range_map(start_ip, end_ip)加联合索引,否则 JOIN 变全表扫描 - 避免用
ip_int >= start_ip AND ip_int —— 语义等价但优化器有时无法命中索引 - 如果存在重叠区间,
LEFT JOIN会返回多行,需加ROW_NUMBER() OVER (PARTITION BY l.id ORDER BY m.start_ip)去重
常见坑:IPv6 怎么办?
这套方法只适用于 IPv4。IPv6 是 128 位,SQL 中无法用原生整型承载(DECIMAL(39,0) 可存但计算慢、索引失效)。真实场景中建议:
- 业务层提前识别协议,IPv6 请求统一打标为
'ipv6'或丢入单独宽表 - 数据库里用
VARCHAR(39)存标准化格式(LOWER(INET6_NTOA(INET6_ATON(ip_str)))),查时用前缀匹配(如LIKE '2001:db8:%') - 别试图在 SQL 里做 IPv6 子网计算 —— 没有高效且跨库的解法
真正麻烦的不是转换公式,而是确保所有环节(采集、清洗、存储、查询)对 IP 的解释完全一致:同一个 "127.0.0.1" 在日志里是字符串,在维度表里是整数,在 JOIN 条件里又变成字符串 —— 这种隐式不一致比算法本身更容易导致归类错误。

















