本篇学习笔记的主要内容:
介绍MySQL支持的各种数据类型(常用),并讲解其主要特点。
MySQL支持多种数据类型,主要包括数值类型、日期和时间类型、字符串类型。
数值类型
MySQL的数值类型包括整数类型、浮点数类型、定点数类型、位类型。
整数类型
MySQL支持的整数类型有tinyint、smallint、mediumint、int、bigint(范围从小到大)。
zerofill
我们在定义整数类型时可以在类型名称后面的小括号内指定显示宽度,例如int(5),当插入的数值宽度小于5位时,MySQL会在数值前面填充宽度。对于int类型如果不手动指定宽度,则默认为int(11)。
显示宽度一般是配合zerofill来使用,即当插入的数值位数未达到指定的显示宽度时,缺少几位就会在数值前填充几个0
图1
图1,我们创建表t_1,两个字段分别为id1和id2,都是int类型。其中id2我们指定了显示宽度为5,而id1没有手动指定显示宽度,因此它的显示宽度会取默认值11。
图2
图2,我们向表中插入一条数据后再将其查询出来,虽然现在id1和id2查询出来的数值都是1,但由于id1在定义时没有指定显示宽度,因此在插入数值1后,其前面10位都被填充了宽度。而id2由于指定了显示宽度,因此其面前只有4位被填充宽度。
图3
图4
图3、图4,为了更加直观的看到填充宽度的效果,我们将id1和id2的定义稍作修改,使用zerofill来填充宽度。
图5
图5,在使用了zerofill后,我们可以看到数值前面被0填充宽度的效果。那么我们在进行查询时使用1或00001作为条件可以得到结果吗?
图6
图6,可以看到在使用1或00001作为查询条件时,能查出id2对应的数值。但这里要注意的是在MySQL中实际存储的值仍然是1,而不是00001,因为00001并不是一种整数的表现形式,而是一种字符串的表现形式,下面的图7将证明这个问题。
图7
图7,我们在查询时使用了hex()函数作为对比,可以看到使用hex()函数得到的值是1,假若hex()得到的值是3030303031(字符串1的16进制为31,字符串0的16进制为30),则可以肯定在MySQL中是以00001的字符串形式进行存储的,但很明显这里并不是。
注:hex()函数可以将一个数字或字符串转换为十六进制格式的字符串
对于指定显示宽度的做法,联想到一个问题,在id2定义为int(5)的情况下,如果插入超过显示宽度的值,会怎么样呢?
图8
图8,向id2插入长度为6位的数值111111时,MySQL没有报任何错误也没有将111111截断。因此说明了显示宽度并不会对插入的数值长度产生限制,两者并没有什么关系,除非插入的数值超过了数据类型的范围,见图9。
图9
图9,可以看到虽然插入成功但MySQL有一个警告(当MySQL的SQL Mode为严格模式时,该插入行为将不能够被完成,同时MySQL会报ERROR),我们在将数据查询出来时可以看到MySQL对原本插入的数据进行了截取,保留值为4294967295。
注:int数据类型有符号的最小值为-2147483648,最大值为2147483647,无符号的最小值为0,最大值为4294967295
额外知识点:
其实对于显示宽度来说,只有配合使用了zerofill,显示宽度才有意义,否则就让显示宽度为默认值就可以了。不要以为指定显示宽度会对整数类型的取值范围有什么影响,两者之间没有任何关系,而说到整数类型的取值范围只有unsigned才会对其产生影响。
unsigned
当我们在定义整数类型时使用了zerofill,MySQL会为我们自动对该列再添加unsigned(图3、图4对列添加了zerofill后,再查看表的DDL [数据库定义语句],会发现列多了unsigned,详见图10)。这是因为当使用了zerofill后,插入该列的值就不可能为负数了,因此自动添加unsigned也是理所应当的,同时unsigned也会增加整数类型最大值的取值范围。
图10
auto_increment
整数类型还有一个属性就是auto_increment,而且这个属性还是整数类型特有的。auto_increment的作用就是使列值保持自动增长,auto_increment的值默认从1开始,也可以手动设置其初始值。对被设置为auto_increment的列插入null值时,实际插入的值是该列当前最大值加1(null并不会影响到被设置为auto_increment列的数据插入,列会正常的进行自动增长)。
当一个列被设置为auto_increment时,通常还需要为该列设置not null和primary key(主键,一般被设置为auto_increment的列会作为主键使用,这里只是说一般,也有非主键的情况)。
另外需要提醒的是一张表中最多只能有一个字段被设置为auto_increment。
浮点数和定点数
这两者都是用来表示小数的,浮点数包括float(单精度)、double(双精度),定点数仅为decimal。两者在定义时都可以指定其精度和标度,精度是指一共显示多少位数字(整数位+小数位),标度是指精确到小数点后多少位,表现形式如:decimal(15,2),这里的精度是15位(整数13位,小数2位),标度是2位。
需要说明的是定点数在MySQL内部是以字符串的形式来保存的,属于准确存储,但表现出来的是小数,它比浮点数更精确。
图11
图12
图13
图11,我们创建一张表,字段id的数据类型为decimal(5,2),如图12在向表里插入超过标度的值时,虽然插入成功但是插入时的数据却被截断了,这里发生了四舍五入。
图13我们向表里尝试插入超过精度的值,难道也会发生截断并四舍五入?两个值会分别显示为123.12和124.12吗?从结果来看明显不是,我们的猜测是完全错误的。在超过精度的情况下,虽然插入成功但插入的值却是指定精度和标度下的最大值,例如(5,2)下的最大值为999.99。
若是在SQL Mode严格模式下,上述这些插入操作将不能被执行成功且MySQL会报ERROR。
额外知识点:
单精度和双精度的区别,这两者的区别可别理解为单精度是精确到小数点后一位,而双精度是精确到小数点后两位,这明显是错误的。实际上由于float的有效位数是7位,double的有效位数是16位,因此单精度、双精度其实是指代这里的有效位数。
另外需要注意的是有效位数并不等于精确位数,纵然float可以表示到小数点后7位,但只有前6位是精确的,第7位很可能造成数据误差。而对于double来说只有前15位是精确的,第16位也很可能造成数据误差。
额外知识点:
关于float、double精度丢失的问题,实际上就是被扩展或截断了,究其缘由是因为存取时标度不一致所导致的。在录入数据时若数据的标度与定义列数据类型时设置的标度不一致,则会导致存入时以近似的值来存储,这就造成了我们上面说到的精度丢失。
那在什么情况下float、double的精度不会丢失呢?其实根据上面出问题的情况,我们可以想到当数据标度与类型标度一样时(录入数据的标度与定义列数据类型时设置的标度一致),就不会发生精度丢失。
鉴于此,我们常选用decimal类型,小于等于其标度的数据都能被正确录入,不会发生精度丢失,因为其是将数据以字符串的形式来存进数据库的,这就保证了精确性。但并不是说decimal就不会发生精度丢失,虽然它不会发生精度扩展但却会发生精度截断。例如当录入数据的标度大于列数据类型设置的标度时,依然会发生四舍五入。
虽然我们说decimal将数据以字符串的形式存入数据库,同时又会存在精度截断的问题(四舍五入),看似两者有文字描述上的冲突,其实不然。我们这样来理解:decimal将发生了四舍五入的数据以字符串的形式存入了数据库,但表现出来的是小数(一个是存储形式,一个是表现形式),且这个小数的精度不会再发生变化,而不管是以什么精度来获取这个值,它都是四舍五入后以字符串形式存入时的值。
位类型
位类型指的就是BIT,它是用来存放二进制数据的,bit(1)表示存储长度为1位的二进制数据。
图14
图15
我们对图14的表中插入超过位数的数据,从图15的第二个查询结果集中可以发现数据发生了截断,数值2的二进制是10,3的二进制是11,它们的第二位都被截断了。
在图15的第一个查询结果集中,需要说明的是在MySQL命令行窗口中使用select * from t_bit_test是无法看到我们需要的数据的,你只能看到有两个笑脸被显示出来,那既然bit中存放的是二进制数据,我们就使用bin()函数以二进制的形式来显示它们。
日期时间类型
日期时间类型的主要区别如下:
①如果要用来表示年月日时分秒,一般使用datetime类型;
②如果要用来表示年月日,一般使用date类型;
③如果要表示时分秒,一般使用time类型;
④如果只是表示年份,一般使用year类型,需要注意的是5.5.27版本之前(不包含该版本)year类型有2位和4位格式这两种表示,在5.5.27版本之后2位格式的year已经不再被支持,year类型的值都会以YYYY的格式显示。
year(2)被弃用主要是因为两位的显示宽度使得该类型表示变得模糊、不明确(显示的值仅仅是最后两位数字),因此如果在设计数据库时需要使用year数据类型,而MySQL的版本又是低于5.5.27版本的话,建议将year类型定义为year(4)以避免问题(从5.5.27版本开始若创建表时将year类型定义为year(2),实则MySQL会强制将其定义为year(4)。详见图15)。
若在将低版本的MySQL升至5.5.27以后的版本时需要将这块的数据做下处理;
⑤如果需要经常插入系统当前时间或将时间更新为系统当前时间,一般使用timestamp类型。
图15
图16
图16,我们创建了一张表,列d1的数据类型为timestamp,通过查看表的定义可以看到MySQL自动将timestamp类型列的默认值设置为了current_timestamp,即系统当前时间,另外还加上了on update current_timestamp(当该行的数据发生变化时,该列的值会随系统当前时间被更新,详细效果见图17、18)。
图17
图17,我们为t_dt_test表新增列i1,int类型显示宽度为2,宽度不够时用0填充。
图18
图18中我们先为表插入一条数据,可以看到插入的d1值为2016-10-22 15:25:31,接着我们将i1的值由1修改为2,再看d1发现原本存入的时间被更新了,此时的值为2016-10-22 15:26:27,这就是随系统当前时间戳更新的效果。
图19
图19是对timestamp类型的列插入超出限额的值,发现MySQL也会将其值置为0000-00-00 00:00:00,第一条插入的是超出timestamp最小下限的值,第二条插入的是超出timestamp最大上限的值。
对于日期时间类型的数据插入,是允许不严格的语法的。
①任何标点符都可以用做日期部分或时间部分之间的间隔符,例如:”2016*10*24 11/11/11”、”2016*10-24 11@11/11”这两种不严格的格式都是可以正常插入的,详见图20。
②对于日期部分的月和日,如果其值小于10,可以不指定两位数;对于时间部分的时分秒,如果其值小于10,也可以不指定两位数。
③YYYYMMDDHHMMSS和YYMMDDHHMMSS这两种格式的无间隔符字符串或数字,假定其对于日期类型是有意义的,则可以被正常解释并插入数据库,详见图21。
图20
虽然MySQL支持这种不严格的语法,但是建议实际插入数据时以标准格式来进行。
图21
额外知识点:
我们知道若创建表时某列的数据类型为timestamp,则MySQL会为表中的第一个timestamp设置其默认值为current_timestamp,而将列中第二个timestamp的默认值设置为’0000-00-00 00:00:00’,若图22。
图22
t_dt_test_5628表后面的位数5628表示所使用的MySQL版本,这里的版本是5.6.28。
那么假如我们在创建表时就强制将两个timestamp的默认值设置为current_timestamp,会怎么样呢?
图23
图24
我们看到在5.6.28版本的表中,该行为是成功了,但是在5.1.57版本中该行为却是失败的。这是因为在2012年4月10日MySQL发布的5.6.5版本中,对于‘每个表最多只可以自动初始化或更新一个timestamp列为当前日期和时间’的限制已经解除了。
以下内容摘自MySQL 5.6.5版本的发行说明:
“Previously, at most one TIMESTAMP column per table could be automatically initialized or updated to the current date and time. This restriction has been lifted. Any TIMESTAMP column definition can have any combination of DEFAULT CURRENT_TIMESTAMP and ON UPDATE CURRENT_TIMESTAMP clauses. In addition, these clauses now can be used with DATETIME column definitions.”
大意为:
“以前,每个表最多可以自动初始化或更新一个TIMESTAMP列为当前日期和时间。现在此限制已取消。任何TIMESTAMP列的定义都可以具有DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP这两个子句的任意组合。此外,现在这些子句也可以使用在DATETIME列的定义中。”
字符串类型
对字符数据进行存储,MySQL提供了多种字符串类型,常见如char、varchar、text、blob、binary、varbinary等。
char和varchar
两者在声明时都可以指定能存储的字符数,char(M)、varchar(M),这里的M是指字符数而不是字节数,例如将M设置为4,则可以存储4个中文、字母、数字。两者的主要区别是char列的长度固定为创建表时声明的长度,而varchar列的长度则是可变长的。
图25
图25,是我们用来测试char和varchar类型的表,我们向表中插入四个字符的中文、字母、数字,如图26。
图26
这里验证了前面说到的char(M)、varchar(M)的M是指字符数而不是字节数。由于这张表是utf8编码格式的表,这种编码格式下一个中文字符是占用3个字节的,如果M是指代字节,则很明显“测试数据”这四个字符是无法插入的,因为它们需要12个字节才能存储。
另外在插入数据时若插入的字符串尾部包含空格,char会删除掉尾部的这些空格而varchar会保留尾部的这些空格。详见图27,可以看到char对于头部和中间的空格是不做处理的。
图27
为什么char会删除掉尾部空格呢?
首先MySQL官方手册对于char的解释是,在指定了能存储的字符数(M)后,若插入的数据字符数未达到指定能够存储的字符数,则会对这些数据进行右空格填充(也可以理解为进行尾部空格填充)。举个例子,例如插入“测试 ”这个字符串,其长度为3个字符未达到上面定义的4个字符存储长度,MySQL会删除原数据的尾部空格,因为MySQL本身就要进行右空格填充操作,而这个原数据尾部却带有空格,明显会造成冲突。
有人会说,那MySQL只需要填充还缺少的空格数就行了啊,不,MySQL并不会对数据尾部你带几个空格,我需要填充几个空格这种行为做存储记录,因为没必要设计的这么复杂,因此直接将原数据尾部的空格都干掉,然后看达到指定存储的字符还差几个空格,再进行相应数量的右空格填充即可。
当对char数据进行检索的时候,MySQL会自动删除掉先前填充的右空格。这个行为也是可以理解的,因为如果不把自动填充的右空格删除,则进行检索时你所想检索的数据和实际存储的数据根本就不匹配,会导致无法检索出数据的情况。
附官方手册对这一块儿的说明:
“The length of a CHAR column is fixed to the length that you declare when you create the table. The length can be any value from 0 to 255. When CHAR values are stored, they are right-padded with spaces to the specified length. When CHAR values are retrieved, trailing spaces are removed unless the PAD_CHAR_TO_FULL_LENGTH SQL mode is enabled. ”
大意为:
“CHAR列的长度固定为创建表时声明的长度。 长度可以是0到255之间的任意值。当存储CHAR类型的值时,MySQL会用指定长度的空格进行右填充。 而当检索CHAR类型值时,除非启用 PAD_CHAR_TO_FULL_LENGTH 的SQL模式,否则将会删除掉尾部的空格。”
varchar最多能存储多少个字符?
这个问题千万不要想当然,不然结果会出乎你的意料。
虽然我们在定义varchar时能够指定其能存储多少字符,但实际varchar能存储的字符数量是受限于其能存储的字节数的,这个限制字节数为65535,就是说被定义为varchar的列的这一行数据,最多能存储65535个字节。
这里详细说明下:
①如果是非空的varchar,则实际能存储65533个字节,因为还有两个字节是用来标记列长度的(varchar大于255个字节,就需要用2个字节来标记列长度,反之用1个字节来标记列长度);
②如果是可为空的varchar则实际能存储65532个字节,因为非空标记还需要占用一个字节。
最多能存储字符数的计算:
在utf8编码格式下,将varchar定义为非空,再利用公式:(65535-2)/3=21844余1,就能得到其最多能存储的字符数为21844个。若需要存储的字符串超过了这个部分,则应该根据实际情况来选择用tinytext、mediumtext、text或longtext作为该列的数据类型。
额外知识点:
字符所占用的字节数与编码格式有关。
一个中文字符在ISO-8859-1编码格式下,占用一个字节;在GB2312、GBK编码格式下,占用两个字节;在UTF-8编码格式下,占用三个字节;
一个数字或字母在ISO-8859-1、GB2312、GBK、UTF-8编码格式下占用一个字节。
---------------------以上为所有内容---------------------