【技术面试高频】MySQL 存储 IP 地址:你还在用 VARCHAR?

面试官:「在 MySQL 中存储 IP 地址用什么数据类型?」

你:「用 VARCHAR(15) 存字符串吧,直观...」

面试官:「那查询 IP 段 192.168.1.0 - 192.168.1.255 的访问记录呢?」

你:「用 LIKE '192.168.1.%' ...」

面试官:「数据量有几千万条呢?」

你:「......」

如果你遇到过这种尴尬场面,那这篇文章一定要收藏起来!


目录


一、先说结论

MySQL 存储 IP 地址有三种主流方案,各有优缺点:

存储方案 数据类型 示例值 存储空间
字符串 VARCHAR(15) '192.168.1.1' 11-15 字节
整数 INT UNSIGNED 3232235777 4 字节
二进制 VARBINARY(16) 0xC0A80101 4-16 字节

推荐方案:

  • IPv4 :使用 INT UNSIGNED + INET_ATON/INET_NTOA 转换
  • IPv6 :使用 VARBINARY(16) + INET6_ATON/INET6_NTOA 转换

二、三种存储方案对比

方案一:VARCHAR(15) 存储字符串

复制代码
优点:
✅ 直观、可读性好、人类友好
✅ 存储格式即为显示格式,无需转换
✅ 便于人工查看和调试

缺点:
❌ 存储空间大(11-15 字节)
❌ 比较运算慢(字符串逐字节比较)
❌ 无法做数值范围查询
❌ 无法利用索引高效定位

方案二:INT UNSIGNED 存储整数

复制代码
优点:
✅ 存储空间小(仅 4 字节)
✅ 比较运算快(单次 CPU 指令)
✅ 可做范围查询和排序
✅ 可利用索引高效定位
✅ MySQL 提供转换函数

缺点:
❌ 不可读,需要转换函数
❌ 只能存储 IPv4

方案三:VARBINARY(16) 存储二进制

复制代码
优点:
✅ 支持 IPv4 和 IPv6 统一存储
✅ 存储空间适中(4-16 字节)
✅ 可利用索引高效查询
✅ 安全性相对较好

缺点:
❌ 不可读,需要转换函数
❌ 需要使用专用转换函数

三、原理层面:为什么整数存储更高效?

3.1 字符串比较 vs 数值比较

flowchart LR subgraph 字符串比较 S1["'192.168.1.1'"] --> S2["逐字节比较ASCII码"] S2 --> S3["需要比较11次"] S3 --> S4["最后一位才区分"] end subgraph 数值比较 N1["3232235777"] --> N2["单次CPU指令"] N2 --> N3["直接判断大小"] N3 --> N4["可利用索引"] end

字符串比较 "192.168.1.1" vs "192.168.1.2":

  • 逐字节比较 ASCII 码
  • 需要比较 11 次才能得出结果
  • "1.1" 和 "1.2" 的最后一位才区分

数值比较 3232235777 vs 3232235778:

  • 单次 CPU 指令完成
  • 直接判断大小关系
  • 可利用索引高效定位

3.2 INET_ATON 转换原理

ini 复制代码
IP: 192.168.1.1

计算过程:
192 * 256³ + 168 * 256² + 1 * 256¹ + 1 * 256⁰
= 3221225472 + 1107296256 + 256 + 1
= 3232235777

二进制表示:
11000000 10101000 00000001 00000001
十六进制:0xC0A80101

四、MySQL 内置转换函数

4.1 IPv4 转换函数

sql 复制代码
-- 字符串 → 整数
INET_ATON('192.168.1.1')
-- 结果:3232235777

-- 整数 → 字符串
INET_NTOA(3232235777)
-- 结果:'192.168.1.1'

4.2 IPv6 转换函数

sql 复制代码
-- 字符串 → 二进制
INET6_ATON('2001:0db8:85a3::8a2e:0370:7334')
-- 返回 VARBINARY 类型

-- 二进制 → 字符串
INET6_NTOA(binary_col)
-- 返回可读的 IPv6 地址

4.3 IP 验证函数

sql 复制代码
-- 判断是否为有效 IPv4
IS_IPV4('192.168.1.1')
-- 返回 1(真)或 0(假)

-- 判断是否为有效 IPv6
IS_IPV6('::1')
-- 返回 1 或 0

五、存储空间与查询性能对比

指标 VARCHAR(15) INT UNSIGNED VARBINARY(16)
IPv4 存储空间 15 字节 4 字节 4 字节
IPv6 存储空间 45 字节 不支持 16 字节
比较速度 最慢 最快 快
索引效率 低 高 高
可读性 高 低 低
范围查询 需 LIKE 支持 BETWEEN 支持

性能差距有多大?

假设有 1000 万条数据:

  • VARCHAR 查询可能需要 数秒
  • INT 查询通常只需 几十毫秒

六、IPv4 vs IPv6 统一存储方案

如果你的系统需要同时支持 IPv4 和 IPv6,推荐使用统一的 VARBINARY(16) 方案:

sql 复制代码
-- 统一使用 VARBINARY(16) 存储
CREATE TABLE access_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    ip_address VARBINARY(16) NOT NULL,
    access_time DATETIME,
    url VARCHAR(255),
    INDEX idx_ip (ip_address)
);

-- 插入 IPv4
INSERT INTO access_log (ip_address, access_time, url) 
VALUES (INET6_ATON('192.168.1.1'), NOW(), '/index');

-- 插入 IPv6
INSERT INTO access_log (ip_address, access_time, url) 
VALUES (INET6_ATON('::1'), NOW(), '/index');

-- 查询时转换显示
SELECT 
    id,
    INET6_NTOA(ip_address) AS ip_address,
    access_time,
    url
FROM access_log;

-- IP 段查询
SELECT * FROM access_log 
WHERE ip_address = INET6_ATON('192.168.1.1');

七、实际应用场景选择

场景 推荐方案 理由
用户 IP 记录 INT UNSIGNED 量大、查询频繁、需范围统计
日志分析 VARCHAR(45) 快速入库、后期离线分析
白名单配置 INT UNSIGNED 固定配置、查询为主
混合 IPv4/IPv6 VARBINARY(16) 统一格式、兼容未来

八、代码实战示例

8.1 创建表结构

sql 复制代码
-- 创建用户访问记录表
CREATE TABLE user_visits (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    ip_v4 INT UNSIGNED NOT NULL,           -- IPv4 存储
    ip_v6 VARBINARY(16),                    -- IPv6 存储
    visit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    -- 可对整数 IP 建索引
    INDEX idx_ip (ip_v4),
    INDEX idx_user_time (user_id, visit_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

8.2 插入数据

sql 复制代码
-- 使用转换函数插入数据
INSERT INTO user_visits (user_id, ip_v4, ip_v6) VALUES 
(1001, INET_ATON('192.168.1.100'), NULL),
(1002, INET_ATON('10.0.0.1'), NULL),
(1003, INET_ATON('172.16.0.1'), INET6_ATON('::1'));

8.3 查询数据

sql 复制代码
-- 查询并转换为可读格式
SELECT 
    user_id,
    INET_NTOA(ip_v4) AS ip_address,        -- 整数转字符串
    INET6_NTOA(ip_v6) AS ipv6_address,
    visit_time
FROM user_visits
WHERE ip_v4 BETWEEN INET_ATON('192.168.0.0') 
                AND INET_ATON('192.168.255.255');

8.4 IP 段统计

sql 复制代码
-- 统计 IP 段访问量(按 /24 网段分组)
SELECT 
    INET_NTOA(ip_v4 DIV 256 * 256) AS ip_segment,
    COUNT(*) AS visit_count
FROM user_visits
GROUP BY ip_v4 DIV 256
ORDER BY visit_count DESC
LIMIT 10;

九、常见误区避坑

误区 正确理解
❌ 用 INT 存储 IP ✅ 必须用 INT UNSIGNED,IPv4 最大值 3232235777 超出会溢出
❌ 查询时直接用字符串比较 ✅ 必须用 INET_ATON 转换:WHERE ip = INET_ATON('192.168.1.1')
❌ IPv4 和 IPv6 混用同一字段 ✅ 分别使用 INT UNSIGNED 和 VARBINARY(16),或统一用 VARBINARY(16)
❌ VARCHAR 更安全 ✅ IP 不是敏感信息,VARBINARY 并不更安全,只是性能较差
❌ 字符串比较效率还行 ✅ 大数据量下,字符串比较无法利用索引,性能差距巨大

十、面试回答模板

当面试官问「MySQL 存储 IP 用什么数据类型」时,可以这样回答:

面试回答示例:

MySQL 存储 IP 地址有三种方案:

  1. VARCHAR(15):直观可读,但存储空间大、查询慢
  2. INT UNSIGNED:存储空间小(4字节)、查询快、可建索引
  3. VARBINARY(16):支持 IPv4/IPv6 统一存储

我推荐使用 INT UNSIGNED + INET_ATON/INET_NTOA 存储 IPv4,原因有三点:

  • 存储空间小,比 VARCHAR 节省约 70%
  • 比较运算快,单次 CPU 指令完成
  • 可利用索引高效查询,支持范围查询

IPv6 则使用 VARBINARY(16) + INET6_ATON/INET6_NTOA。


总结

IP 地址存储推荐方案:

scss 复制代码
┌────────────────────────────────────────────────────────────┐
│                     IP 存储方案选择                         │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  IPv4 → INT UNSIGNED + INET_ATON/INET_NTOA                │
│         ✅ 4字节 | ✅ 查询快 | ✅ 可建索引                  │
│                                                            │
│  IPv6 → VARBINARY(16) + INET6_ATON/INET6_NTOA             │
│         ✅ 16字节 | ✅ 统一格式                            │
│                                                            │
│  混合 → VARBINARY(16) + INET6_ATON/INET6_NTOA             │
│         ✅ 兼容 IPv4/IPv6                                  │
│                                                            │
└────────────────────────────────────────────────────────────┘
相关推荐
Rain的Java大神之路2 小时前
Java 三层架构,90% 的人只答对一半:Controller 不是瘦,Service 不是透传,Dao 不是只写 SQL
java·后端·面试
壹玖玖肆2 小时前
Docker 部署 openGauss 7.0(aarch64)MySQL 兼容模式建库与数据恢复完整实践
mysql·docker·容器
hgf10112 小时前
大内密探 · 案卷 1.2:一夫当关,万夫莫开(Receive Enable)
数据库·mysql
怕浪猫2 小时前
前端转全栈最低成本的技术栈选择
前端·javascript·面试
子沫20203 小时前
mysql慢sql
sql·mysql
2501_933923253 小时前
MySQL CRUD 详解:表数据插入、更新与删除,附生产避坑指南
数据库·mysql
kaixin_啊啊3 小时前
飞牛部署Mtab完整实战:Docker安装、MySQL建库与固定地址访问
mysql·docker·容器
考研保研资料分享3 小时前
2022护理保研浙江大学:湘雅PICOS追问、健康宣教口译与科研准备
面试
程序员Sunday12 小时前
TypeScript 的 any、unknown、never 有什么区别?
前端·面试·typescript