IP字符串需转为32位整数再按数值区间分组,因字典序不等于数值序;安全转换须清洗空格、补全段并跨库适配类型;网段聚合用FLOOR(ip_int/256)*256等公式;恶意IP段应解耦至独立表并建索引。

直接对 IP 字符串 GROUP BY 或用 LIKE '192.168.%' 分组,统计结果不可信——字典序不等于数值大小,会漏判、误吞、跨段错配。必须转成整数再按数值区间归并。
把 IP 字符串安全转成 32 位整数(跨库通用写法)
所有主流 SQL 引擎都不支持原生 CIDR 运算,但都支持字符串拆解和整型计算。手算公式是唯一可控路径:a * 16777216 + b * 65536 + c * 256 + d。关键在“安全取段”:空段、尾随空格、缺段都会让 SUBSTRING_INDEX 返回空或错误值。
- MySQL:用
SUBSTRING_INDEX(SUBSTRING_INDEX(ip_str, '.', 2), '.', -1)取第二段,避免SUBSTRING_INDEX(ip_str, '.', 2)直接返回"10.1"导致后续计算失败 - PostgreSQL:必须显式类型转换,
SPLIT_PART(ip_str, '.', 1)::int,否则报错或静默转 0 - SQL Server:用
PARSENAME(REPLACE(ip_str, '.', ' '), 4)取第一段,但只支持 4 段;超长或含 IPv6 会直接报错 - 结果必须存为
BIGINT或无符号类型——IPv4 最大值4294967295超出有符号INT范围,MySQL 中若用INT会变负数
按固定网段前缀(如 /24)分组聚合恶意访问
生产中极少需要动态掩码长度。硬编码比“通用函数”更稳、更易读,也避免嵌套函数导致索引失效。别用 WHERE ip_str LIKE '137.220.155.%'——它会漏掉 137.220.155.0,也会吞下 137.220.155.100(如果日志里真有这个 IP,但不属于同一 /24)。
- /24 网段:用
FLOOR(ip_int / 256) * 256得到该网段起始地址整数,再用INET_NTOA()(MySQL)或自定义格式化函数转回可读形式 - /16 网段:用
FLOOR(ip_int / 65536) * 65536,兼容 PostgreSQL/SQL Server - 聚合时加
HAVING COUNT(*) > 100过滤低频噪音,避免把扫描器试探流量混入结果 - 如果原始表没预存
ip_int,临时分析可用 CTE:WITH log_with_int AS (SELECT *, (SUBSTRING_INDEX(...) * 16777216 + ...) AS ip_int FROM access_log WHERE msg LIKE '%rate limit%')
按自定义恶意 IP 段范围做分类统计(如黑产团伙控制网段)
硬写 CASE WHEN ip_int BETWEEN 2358878214 AND 2358878215 THEN '银狐-香港IDC' 维护成本高,且无法走索引。真实对抗场景中,IP 段来源是动态更新的(比如你知识库里列出的 177.4.3.114、157.90.33.74 等),必须解耦。
- 建一张
malicious_ip_range表,字段为range_name、start_ip(BIGINT)、end_ip(BIGINT),把知识库中 TOP 20 恶意 IP 扩展成合理网段(如177.4.3.114/28→start_ip=2970402928,end_ip=2970402943) - 关联查询用
JOIN:ON log.ip_int BETWEEN map.start_ip AND map.end_ip,不是WHERE子查询 -
log.ip_int和map.start_ip/map.end_ip都必须建索引,否则 JOIN 会全表扫描,千万级日志下秒变卡死 - 若日志表未预存
ip_int,又需高频查,别在每次查询里重复算表达式——性能断崖下跌。应在线上补字段、写触发器或应用层同步写入
最常被忽略的一点:IP 字符串字段常含前后空格、换行符或末尾句点(如日志解析残留 "137.220.155.13\n" 或 "104.143.42.1."),不清洗就转整数,结果全错。务必在转整数前用 TRIM() 或正则清洗,SLS 场景下甚至得先用 regexp_extract(msg, 'true-client-ip: (\d+\.\d+\.\d+\.\d+)', 1) 提纯。

















