当前位置: 首页 > 科技观察

MySQL如何有效地存储IP地址?

时间:2023-03-17 20:01:57 科技观察

前几天,阿芬的一个朋友去面试了。他回来告诉我,面试官问他IP地址是如何存储在数据库中的。:这么简单的问题,怕你别看不起我)上一段要看。毕竟IP地址本来就是一个字符串,以字符串类型存入数据库无可厚非。不过,阿芬,我是一个喜欢换位思考的人。站在面试官的角度,你觉得我会问这么低级的问题吗?那么档案当然是负面的。所以,面试官想知道的是你是否会对这个问题有深入的思考,从中一定程度上可以判断你在正常情况下只是一个简单的“搬砖”coder开发,或者有灵魂的编码器。前言针对这个问题,我先声明,IP地址以字符串的形式保存在数据库中是完全可以的。那么你可能会有疑惑?既然没问题,还在这里比什么?虽然,这是一个不一致的话题,但是除了存储字符串,我们还有其他的存储方式。例如,它更常用于将IP地址存储为int类型的数据。虽然这种存储方式实现起来并不复杂,但是这种方式的思路也在一定程度上说明了你是一个善于思考的人,对底层数据基础把握的很好。比较到位。因为一个int类型的数据占用4个字节,每个字节为8位,其范围是0~(2^8-1),而ipv4地址可以分为4段,每段的范围是0~255,正好可以保存下来,所以稍微转换一下,就巧妙地将IP地址以最小的空间存储在了数据库中(以下描述如无特别说明,均指ipv4地址)。你可能认为这个小变化与它无关,但是当数据量增加时,15字节和4字节之间的差异会让你大吃一惊。所以,在设计数据库的时候,要使用合适的字段类型,够用就够用,能省就省。正如《高性能MySQL 第3版》的4.1.7节中,作者建议在存储IPv4地址时,应该使用32位无符号整数(UNSIGNEDINT)来存储IP地址,而不是字符串。与字符串存储相比,使用无符号整数存储有以下优点:节省空间,无论是数据存储空间还是索引存储空间,使用范围查询(BETWEEN...AND)都方便,效率更高。一般在保存IPv4地址时,一个IPv4要求最少7个字符,最多15个字符,所以使用VARCHAR(15)即可。MySQL在保存变长字符串时,需要额外的一个字节来保存字符串的长度。而如果使用无符号整数来存储,只需要4个字节。另外,也可以用4个字段分别存储IPv4的每一部分,但通常这样在存储空间和查询效率上应该不会很高(虽然有些场景适合这样存储)。但是,使用无符号整数进行存储也有不便读取和需要手动转换的缺点。工具类实现转换,将IP地址以int类型保存在数据库中。一种是通过java代码中的移位运算和&计算得到对应的值:packagecom.java.mmzsit;/***@author:mmzsblog*@description:ipv4地址转换*@date:2020/5/2722:43*/publicclassIpv4Covert{publicstaticvoidmain(String[]args){Stringip="10.108.149.219";//step1:分解IP字符串,并对应写入字节数组byte[]ip1=ipToBytes(ip);//step2:对字节数组中的每个字节进行左移处理,对应整型变量的4个字节intip2=bytesToInt(ip1);System.out.println("integerip---->"+ip2);//step3:右移整型变量,恢复IP字符串Stringip3=intToIp(ip2);System.out.println("stringip---->"+ip3);}/***IP地址转int*@paramipAddr*@returnint*/publicstaticbyte[]ipToBytesByReg(StringipAddr){byte[]ret=newbyte[4];try{String[]ipArr=ipAddr.split("\\.");ret[0]=(字节)(Integer.parseInt(ipArr[0])&0xFF);ret[1]=(byte)(Integer.parseInt(ipArr[1])&0xFF);ret[2]=(byte)(Integer.parseInt(ipArr[2])&0xFF);ret[3]=(byte)(Integer.parseInt(ipArr[3])&0xFF);returnret;}catch(Exceptione){thrownewIllegalArgumentException(ipAddr+"isinvalidIP");}}/***第一步将IP地址分解成btye数组*/publicstaticbyte[]ipToBytes(StringipAddr){//初始化字节数组,定义长度为4byte[]ret=newbyte[4];try{String[]ipArr=ipAddr.split("\\.");//依次将字符串数组写入字节数组ret[0]=(byte)(Integer.parseInt(ipArr[0]));ret[1]=(byte)(Integer.parseInt(ipArr[1]));ret[2]=(byte)(Integer.parseInt(ipArr[2]));ret[3]=(byte)(Integer.parseInt(ipArr[3]));returnret;}catch(Exceptione){thrownewIllegalArgumentException("invalidIP:"+ipAddr);}}/***byte[]->int*根据bit的原理操作:每个字节强制转换为8位二进制码,然后依次左移8位,对应Int变量的4个字节*/publicstaticintbytesToInt(byte[]bytes){//同时移位先强制指定位数intaddr=bytes[3]&0xFF;addr|=((bytes[2]<<8)&0xFF00);addr|=((bytes[1]<<16)&0xFF0000);地址|=((字节[0]<<24)&0xFF000000);返回地址r;}/***Int->stringaddress**@paramipInt*@returnString*/publicstaticStringintToIp(intipInt){//先强制转换为二进制,然后移位处理returnnewStringBuilder()//移动3个字节(24位)到获取IP地址第一段的权利,即byte[0]。为了防止符号位为1,也就是负数,最后&0xFF.append(((ipInt&0xFF000000)>>24)&0xFF).append('.').append((ipInt&0xFF0000)>>16).append('.').append((ipInt&0xFF00)>>8).append('.').append((ipInt&0xFF)).toString();}}其实这是一种二元思维,也是计算技术中广泛使用的数系。虽然不经常用到,但是有助于加强我们对机器语言的理解,提高我们的编码水平,尤其是在面对资源受限(运算和内存)的场景下,帮助我们分析和优化问题另一种实现数据库函数转换的方法是通过数据库自带的函数INET_ATON和INET_NTOA进行转换:mysql>SELECTINET_ATON('192.168.0.1');+---------------------------+|INET_ATON('192.168.0.1')|+------------------------+|3232235521|+------------------------+1rowinsetmysql>SELECTINET_NTOA(3232235521);+-----------------------+|INET_NTOA(3232235521)|+--------------------+|192.168.0.1|+----------------------+1rowinset如果是IPv6地址,使用函数INET6_ATON和INET6_NTOA转换:mysql>SELECTHEX(INET6_ATON('1030::C9B4:FF12:48AA:1A2B'));+--------------------------------------------+|HEX(INET6_ATON('1030::C9B4:FF12:48AA:1A2B'))|+------------------------------------------+|1030000000000000C9B4FF1248AA1A2B|+----------------------------------------------+1rowinsetmysql>SELECTINET6_NTOA(UNHEX('1030000000000000C9B4FF1248AA1A2B'));+------------------------------------------------------+|INET6_NTOA(UNHEX('1030000000000000C9B4FF1248AA1A2B'))|+---------------------------------------------------------+|1030::c9b4:ff12:48aa:1a2b|+------------------------------------------------------+1rowinset然后定义数据库为varbinary类型,分配128bits空间(因为ipv6使用128bits,16字节);或者定义为char类型,分配32bits的空间,如果使用数据库的功能,只需要在IP地址存入数据库的时候做一点转换,方便快捷;而这里,你不认为把IP地址转换成数字存储是一个好的选择嘛,毕竟数据库已经帮我们考虑到了这一点,也间接证明了这确实是一个可行的好存储方式。

最新推荐
猜你喜欢