论IP地址在数据库中应该用何种形式存储
這引發(fā)我的思考——緣起
當(dāng)設(shè)計(jì)一個(gè)數(shù)據(jù)表時(shí),考慮使用何種列的數(shù)據(jù)類型對(duì)性能有比較大的影響,如存儲(chǔ)空間、查詢開銷等。甚至還影響到一些操作,如ip地址以字符串的形式存儲(chǔ)在數(shù)據(jù)庫(kù)中,就不可以直接比較大小。還有一點(diǎn)需要考慮,那就是可讀性!數(shù)據(jù)雖然是存儲(chǔ)在數(shù)據(jù)庫(kù)中,但也要考慮到可讀性問題。
本文要探討的是“IP地址在數(shù)據(jù)庫(kù)中,應(yīng)該使用何種形式存儲(chǔ)?”,文章將以實(shí)驗(yàn)為基礎(chǔ)介紹使用何種形式比較適合。
1、感性認(rèn)識(shí)
大家都知道ip地址分為ipv4、ipv6,這里我以ipv4為例介紹,ipv6原理是一樣的。ipv4的小為32bits(或者說是4Bytes),在使用過程中,我們通常是用點(diǎn)分十進(jìn)制格式,如192.168.120.65。如何把"192.168.120.65"存儲(chǔ)到數(shù)據(jù)庫(kù)中呢?
我們考慮下面三個(gè)因素:
- 可讀性
- 存儲(chǔ)效率
- 查詢效率
把"192.168.120.65"存儲(chǔ)到數(shù)據(jù)庫(kù)中有多少中可行方法呢?見下表所示:
| 數(shù)據(jù)類型 | 大小 | 注釋 |
| varchar(15) | 占7~15字節(jié) | 可讀性最好(192.168.120.65),但是最費(fèi)存儲(chǔ)空間 |
| bigint | 8 字節(jié) | 可以將ip地址存儲(chǔ)為類似192168120065的格式,這種可讀性稍差,也比較費(fèi)存儲(chǔ)空間 |
| int | 4 字節(jié) | 這種可讀性很差,會(huì)存儲(chǔ)為1084782657,由192*16777216+168*65536+120*256+65-2147483648計(jì)算所得,占用存儲(chǔ)空間少。 |
| tinyint | 4 字節(jié) | 用4個(gè)字段來分開存儲(chǔ)ip地址,可讀性稍差(分別為192, 168, 120, 65),存儲(chǔ)空間占用少 |
| varbinary(4) | 4 字節(jié) | 可讀性差(0xC0A87841),存儲(chǔ)空間占用少 |
從大小來看,依次varchar(15)> bigint> int、tinyint、varbinary(4)。
從可讀性來看,依次是varchar(15)> bigint> tinyint> varbinary(4)>int。
從查詢效率來看,
綜合考慮,似乎tinyint比較好,其次是varbinary(4)。但是tinyint需要占多個(gè)表字段,而varbinary只需要占用一個(gè)字段即可。正確性還有待下面的實(shí)驗(yàn)檢查!!!
2、理性認(rèn)識(shí)
本小節(jié)通過創(chuàng)建5張表,分別用上述5中數(shù)據(jù)類型存儲(chǔ)ip地址,每張表插入1,000,000條記錄。說明為了方便消除差異,這些表中插入的都是192.168.120.65。建表和插入數(shù)據(jù)的sql語(yǔ)句如下(說明:插入1,000,000條記錄要花挺長(zhǎng)時(shí)間的,如果你要自己實(shí)驗(yàn),可以考慮少插入點(diǎn)數(shù)據(jù)):
建表和插入數(shù)據(jù)的sql語(yǔ)句create database ip_address_test; gouse ip_address_test /*****it defines ip address as varchar(15)*****/ create table ip_address_varchar(id int identity(1,1) not null primary key,ipAddress varchar(15) ); /*****it defines ip address as bigint*****/ create table ip_address_bigint(id int identity(1,1) not null primary key,ipAddress bigint ); /*****it defines ip address as int*****/ create table ip_address_int(id int identity(1,1) not null primary key,ipAddress int ); /*****it defines ip address as tinyint*****/ create table ip_address_tinyint(id int identity(1,1) not null primary key,ip_address1 tinyint,ip_address2 tinyint,ip_address3 tinyint, ip_address4 tinyint ); /*****it defines ip address as varbinary(4)*****/ create table ip_address_varbinary(id int identity(1,1) not null primary key,ipAddress1 varbinary(4) );/*****insert data into tables*****/ declare @i int, @ip varchar(15) set @i = 0 set @ip = '192.168.120.65' while @i < 1000000 begin /**** insert into ip_address_varchar values ****/insert ip_address_varchar values(@ip) /**** insert into ip_address_bigint values ****/insert ip_address_bigint values(convert( bigint,right('000'+convert(varchar(3), parsename(@ip, 4)),3)+right('000'+convert(varchar(3), parsename(@ip, 3)),3)+right('000'+convert(varchar(3), parsename(@ip, 2)),3)+right('000'+convert(varchar(3), parsename(@ip, 1)),3)))/**** insert into ip_address_int values ****/insert ip_address_int values(cast((cast(parsename(@ip, 4) as bigint)*16777216)+(cast(parsename(@ip, 3) as bigint)*65536)+(cast(parsename(@ip, 2) as bigint)*256)+cast(parsename(@ip, 1) as bigint)-2147483648as int)) /**** insert into ip_address_tinyint values ****/insert ip_address_tinyint values(convert(tinyint, parsename(@ip, 4)),convert(tinyint, parsename(@ip, 3)),convert(tinyint, parsename(@ip, 2)),convert(tinyint, parsename(@ip, 1))) /**** insert into ip_address_varbinary values ****/insert ip_address_varbinary values(cast( convert(tinyint, parsename(@ip, 4)) as varbinary)+cast( convert(tinyint, parsename(@ip, 3)) as varbinary)+cast( convert(tinyint, parsename(@ip, 2)) as varbinary)+cast( convert(tinyint, parsename(@ip, 1)) as varbinary))set @i = @i + 1 end然后我們執(zhí)行存儲(chǔ)過程sp_spaceused查看空間效率,執(zhí)行下面的sql語(yǔ)句:
exec sp_spaceused ip_address_varchar
exec sp_spaceused ip_address_bigint
exec sp_spaceused ip_address_int
exec sp_spaceused ip_address_tinyint
exec sp_spaceused ip_address_varbinary
可以得到下面的結(jié)果:
說明:上面各個(gè)字段的意思如下表所示
| 列名 | 數(shù)據(jù)類型 | 說明 |
| reserved | varchar(18) | 由數(shù)據(jù)庫(kù)中對(duì)象分配的空間總量。 |
| data | varchar(18) | 數(shù)據(jù)使用的空間總量。 |
| index_size | varchar(18) | 索引使用的空間總量。 |
| unused | varchar(18) | 為數(shù)據(jù)庫(kù)中的對(duì)象保留但尚未使用的空間總量。 |
可以看出,這5張表中的記錄都是1000000,ip_address_varchar占空間最大30792 KB;其次是ip_address_bigint和ip_address_varbinary占用16904 KB;最后是ip_address_int和ip_address_tinyint只占用16904 KB。
所以從可讀性和空間效率上來看,最理想的是用tinyint的數(shù)據(jù)類型存儲(chǔ)ip地址。其次應(yīng)該考慮varbinary(4)和bigint。
理論上bigint肯定要比varbinary占用空間多,可是實(shí)驗(yàn)得出來是一樣的,為什么呢?我查看幫助信息也沒有看出什么異常,varbinary(4)的確是占用4個(gè)字節(jié)、bigint也的確是占用8個(gè)字節(jié),如下圖
如果有知道的,請(qǐng)告訴我一聲!不過讓我從這兩者之間選(信不過數(shù)據(jù)結(jié)果啊),肯定會(huì)選擇使用varbinary(4)而不是bigint。如果能夠證明數(shù)據(jù)結(jié)果沒有錯(cuò),應(yīng)該選擇bigint,因?yàn)樗目勺x性更好!
3、查詢效率
本小節(jié)比較上述5中存儲(chǔ)ip地址的查詢效率。為了比較查詢效率,這里重新插入數(shù)據(jù),消除每張表中的記錄都相同(192.168.120.65),下面編寫存儲(chǔ)過程像數(shù)據(jù)表中隨機(jī)插入1000條記錄(但是保證每張表的數(shù)據(jù)是一樣的)。存儲(chǔ)過程如下:
隨機(jī)插入N條ip地址到5張表中use ip_address_test declare @ip1 tinyint, @ip2 tinyint, @ip3 tinyint, @ip4 tinyint, @i int set @i = 1 while @i <= 1000 beginset @ip1 = FLOOR(256*RAND(cast(cast(left(newid(),8) as varbinary ) as int )) )set @ip2 = FLOOR(256*RAND(cast(cast(left(newid(),8) as varbinary ) as int )) )set @ip3 = FLOOR(256*RAND(cast(cast(left(newid(),8) as varbinary ) as int )) )set @ip4 = FLOOR(256*RAND(cast(cast(left(newid(),8) as varbinary ) as int )) )/**** insert into ip_address_varchar ****/ declare @ip_varchar varchar(15)set @ip_varchar = cast(@ip1 as varchar)+'.'+cast(@ip2 as varchar)+'.'+cast(@ip3 as varchar)+'.'+cast(@ip4 as varchar)insert into ip_address_varchar values(@ip_varchar)/**** insert into ip_address_bigint ****/ declare @ip_bigint bigintset @ip_bigint = convert( bigint,right('000'+convert(varchar(3), @ip1),3)+right('000'+convert(varchar(3), @ip2),3)+right('000'+convert(varchar(3), @ip3),3)+right('000'+convert(varchar(3), @ip4),3))insert into ip_address_bigint values(@ip_bigint)/**** insert into ip_address_int ****/ declare @ip_int intset @ip_int = cast((cast(@ip1 as bigint)*16777216)+(cast(@ip2 as bigint)*65536)+(cast(@ip3 as bigint)*256)+cast(@ip4 as bigint)-2147483648as int)insert into ip_address_int values(@ip_int)/**** insert into ip_address_tinyint ****/ insert into ip_address_tinyint values(@ip1,@ip2,@ip3,@ip4)/**** insert into ip_address_varbinary ****/ declare @ip_varbinary varbinary(4)set @ip_varbinary = cast( convert(tinyint, @ip1) as varbinary)+cast( convert(tinyint, @ip2) as varbinary)+cast( convert(tinyint, @ip3) as varbinary)+cast( convert(tinyint, @ip4) as varbinary)insert into ip_address_varbinary values(@ip_varbinary)set @i = @i + 1 end考慮查找在范圍192.0.0.0~192.255.255.255之間的ip地址的查詢效率問題。說明我忽略了預(yù)處理的開銷,即將192.0.0.0和192.255.255.255轉(zhuǎn)換為上述的5種類型的時(shí)間,代碼中我直接使用了這些值,沒有給出轉(zhuǎn)換過程,具體代碼如下:
查詢192.0.0.0~192.255.255.255之間的ip地址use ip_address_test set statistics profile on set statistics io on set statistics time on /**** find from ip_address_varchar ****/ select * from ip_address_varchar where(cast(parsename(ipAddress, 4) as int) between 192 and 192 and cast(parsename(ipAddress, 3) as int) between 0 and 255 and cast(parsename(ipAddress, 2) as int) between 0 and 255 and cast(parsename(ipAddress, 1) as int) between 0 and 255 ) set statistics profile off set statistics io off set statistics time offset statistics profile on set statistics io on set statistics time on /*****find from ip_address_bigint*****/ select * from ip_address_bigint where(ipAddress between 192000000000 and 192255255255 ) set statistics profile off set statistics io off set statistics time offset statistics profile on set statistics io on set statistics time on /*****find from ip_address_int*****/ select * from ip_address_int where(ipAddress between 1073741824 and 1090519039 ) set statistics profile off set statistics io off set statistics time offset statistics profile on set statistics io on set statistics time on /*****find from ip_address_tinyint*****/ select * from ip_address_tinyint where(ip_address1 between 192 and 192 and ip_address2 between 0 and 255 and ip_address3 between 0 and 255 and ip_address4 between 0 and 255 ) set statistics profile off set statistics io off set statistics time offset statistics profile on set statistics io on set statistics time on /*****find from ip_address_varbinary*****/ select * from ip_address_varbinary where(ipAddress1 between 0xC0000000 and 0xC0FFFFFF ) set statistics profile off set statistics io off set statistics time off
執(zhí)行得到的消息如下:
SQL Server 分析和編譯時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
(5 行受影響)
表 'ip_address_varchar'。掃描計(jì)數(shù) 1,邏輯讀取 6 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。
(3 行受影響)
(1 行受影響)
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 113 毫秒。
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
SQL Server 分析和編譯時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
=============================共115毫秒,ip_address_varchar
(5 行受影響)
表 'ip_address_bigint'。掃描計(jì)數(shù) 1,邏輯讀取 5 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。
(2 行受影響)
(1 行受影響)
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
SQL Server 分析和編譯時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
===================================共4毫秒,ip_address_bigint
(5 行受影響)
表 'ip_address_int'。掃描計(jì)數(shù) 1,邏輯讀取 5 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。
(2 行受影響)
(1 行受影響)
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 146 毫秒。
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
SQL Server 分析和編譯時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
===================================共149毫秒,ip_address_int
(5 行受影響)
表 'ip_address_tinyint'。掃描計(jì)數(shù) 1,邏輯讀取 5 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。
(2 行受影響)
(1 行受影響)
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 85 毫秒。
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
SQL Server 分析和編譯時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
=======================================共88毫秒,ip_address_tinyint
(5 行受影響)
表 'ip_address_varbinary'。掃描計(jì)數(shù) 1,邏輯讀取 5 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。
(2 行受影響)
(1 行受影響)
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 13 毫秒。
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
SQL Server 執(zhí)行時(shí)間:
CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
===================================共15毫秒,ip_address_varbinary
上述結(jié)果只是初略的估計(jì)了效率,可能不太精確,但還是具有一定參考價(jià)值的!我只看ip_address_varbinary(15毫秒)、ip_address_tinyint(88毫秒)、ip_address_bigint(4毫秒)。
效率差距還是挺大的,綜合可讀性、存儲(chǔ)效率、查詢效率,我給這三者排序是:
如果考慮存儲(chǔ)效率,tinyint是最好的!其次是bigint,然后是varbinary(4)
如果更多的是考慮查詢效率,bigint是最好的!其次是varbinary(4),然后是tinyint
如果加我選擇,我會(huì)使用varbinary(4)。
本文轉(zhuǎn)自吳秦博客園博客,原文鏈接:http://www.cnblogs.com/skynet/archive/2011/01/09/1931044.html,如需轉(zhuǎn)載請(qǐng)自行聯(lián)系原作者
總結(jié)
以上是生活随笔為你收集整理的论IP地址在数据库中应该用何种形式存储的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安装中文语言包会导致 Troublesh
- 下一篇: Mysql Oracle 工具推荐