直接用 VARCHAR(15) 存 IP 雖直觀,但查詢效率低、佔用空間大、無法利用數值範圍索引。MySQL 內建 INET_ATON() / INET_NTOA() 實現 IPv4 地址與無符號整數雙向轉換,推薦用 INT UNSIGNED(4 字節)存儲。
-- IP → 數值
SELECT INET_ATON('255.255.255.255'); -- 4294967295 (2^32-1)
SELECT INET_ATON('192.168.1.1'); -- 3232235777
SELECT INET_ATON('10.10.10.10'); -- 168430090
-- 數值 → IP
SELECT INET_NTOA(4294967295); -- 255.255.255.255
SELECT INET_NTOA(3232235777); -- 192.168.1.1
SELECT INET_NTOA(168430090); -- 10.10.10.10
SELECT INET_NTOA(0); -- 0.0.0.0(無 IP 時存 0)
CREATE TABLE `access_log` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`client_ip` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'INET_ATON 存儲',
`url` VARCHAR(255),
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_ip_time` (`client_ip`, `created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Java
String ip = request.getRemoteAddr();
long ipLong = 0;
try {
InetAddress addr = InetAddress.getByName(ip);
byte[] bytes = addr.getAddress(); // IPv4 4 bytes
for (byte b : bytes) {
ipLong = (ipLong << 8) | (b & 0xFF);
}
} catch (UnknownHostException e) { /* fallback 0 */ }
jdbcTemplate.update("INSERT INTO access_log(client_ip, url) VALUES (?, ?)", ipLong, url);
-- 查詢 192.168.1.0/24 網段訪問記錄
SELECT INET_NTOA(client_ip) AS ip, url, created_at
FROM access_log
WHERE client_ip BETWEEN INET_ATON('192.168.1.0') AND INET_ATON('192.168.1.255')
ORDER BY created_at DESC
LIMIT 100;
數值範圍查詢可直接命中 idx_ip_time 索引,極速返回。
| 指標 | VARCHAR(15) | INT UNSIGNED |
|---|---|---|
| 存儲空間 | 7~15 字節 + 1 字節長度前綴 | 固定 4 字節 |
| 比較運算 | 字串逐字符比較 | CPU 單指令整數比較 |
| 索引大小 | 較大 | 極小(1/3~1/4) |
| 範圍查詢 | 需 LIKE/字串比較,不可用索引 | BETWEEN 直接走索引 |
| 排序 | 字典序(10.0.0.1 < 192.168.1.1 錯誤) | 數值序(正確網段順序) |
MySQL 5.6+ 提供 INET6_ATON() / INET6_NTOA() 返回 VARBINARY(16)(128 位)。建表用 VARBINARY(16) 存儲,應用層同樣處理。若僅存 IPv4,也可用 INET6_ATON() 統一,查詢時 INET6_NTOA() 自動識別格式。
INT UNSIGNED,否則 >127.255.255.255 會溢出為負數。0.0.0.0(無 IP),而非 NULL,便於 COUNT/SUM 統計。X-Forwarded-For 首個非內網 IP,再轉換存儲。INET_NTOA(client_ip) 還原可讀 IP。「INT UNSIGNED + INET_ATON/NTOA」是 MySQL 存 IPv4 的黃金標準,兼顧空間、速度、範圍查詢與排序正確性,請在所有新項目中落地。