

进制和字节
MySQL整数类型
很显然,使用的字节数越多,意味着能表示的数值范围就越大,但是也就越耗费存储空间。根据表示一个数占用字节数的不同,MySQL把整数划分成如下所示的类型:
以TINYINT为例,用1个字节,也就是8个位表示有符号数的话,就是既可以表示正数,也可以表示负数的话,需要有一个比特位表示正负号。但是如果表示无符号数的话,也就是只表示非负数的话,就不需要表示正负号,这是有符号数和无符号数的区别。(无符号数需要配合字段属性设置,相关内容请移步MySQL之字段属性ZEROFILL属性模块)

原码、反码、补码详解移步:


二进制表示十进制小数
9.875 = 8 + 0 + 0 + 1 + 0.5 + 0.25 + 0.125 = 1 × 2³ + 0 × 2² + 0 × 2¹ + 1 × 2⁰ + 1 × 2⁻¹ + 1 × 2⁻² + 1 × 2⁻³
符号部分,占用1个比特位即可。
指数部分,视具体浮点数格式而定。
尾数部分,视具体浮点数格式而定。
MySQL的浮点数类型
很显然,我们表示一个浮点数使用的字节数越多,表示尾数和指数的范围就越大,也就是说可以表示的小数范围就越大,MySQL根据表示一个小数需要的不同字节数定义了如下的两种浮点数类型:
以单精度浮点数类型FLOAT类型为例,它占用的4个字节的各个组成部分如下图所示:
另外需要注意的是,虽然有的十进制小数,比如1.875可以被很容易的转换成二进制数1.111,但是更多的小数是无法直接转换成二进制的,比如说0.3,它转换成的二进制小数就是一个无限小数,但是我们现在只能用4个字节或者8个字节来表示这个小数,所以只能进行一些舍入来近似的表示,所以我们说计算机的浮点数表示有时是不精确的。
设置最大位数和小数位数
在定义浮点数类型时,还可以在FLOAT或者DOUBLE后边跟上两个参数,就像这样:
DOUBLE(M, D)
对于我们用户而言,使用的都是十进制小数。如果我们事先知道表中的某个列要存储的小数在一定范围内,我们可以使用FLOAT(M, D)或者DOUBLE(M, D)来限制可以存储到本列中的小数范围。其中:
M
表示该小数最多需要的十进制有效数字个数。
有效数字
个数,比方说对于小数
-2.3
来说有效数字个数就是2,对于小数
0.9
来说有效数字个数就是
1
。
D
表示该小数的小数点后的十进制数字个数。
D
的值就是什么。


此处的M和D的含义与浮点数中的含义一样。M和D对取值范围的影响我们之前在唠叨浮点数的时候已经介绍过了,但是我们又说单精度浮点数类型FLOAT(M, D)占用的字节数一直都是4字节,双精度浮点数DOUBLE(M, D)占用的字节数一直都是8字节,它们占用的存储空间大小并不随着M和D的值的变动而变动。这个所谓的定点数类型DECIMAL(M, D),它占用的存储空间大小取决于M、D的取值。
我们说定点数是一种精确的小数,为了达到精确的目的我们就不能把它转换成二进制小数之后再存储(因为有很多十进制小数转为二进制小数后需要进行舍入操作,导致二进制小数表示的数值是不精确的)。其实转念一想,所谓的小数只是把两个十进制整数用小数点分割开来而已,我们只要把小数点左右的两个十进制整数给存储起来,那不就是精确的了么。比方说对于十进制小数2.38来说,我们可以把这个小数的小数点左右的两个整数,也就是2和38分别保存起来,那么不就相当于保存了一个精确的小数么,这波操作是不是很6。
当然事情并没有这么简单,对于给定M、D值的DECIMAL(M, D)类型,比如DEMCIMAL(16, 4)来说:
首先确定小数点左边的整数最多需要存储的十进制位数是12位,小数点右边的整数需要存储的十进制位数是4位,如图所示:
针对每个组中的十进制数字,将其转换为二进制数字进行存储,根据组中包含的十进制数字位数不同,所需的存储空间大小也不同,具体见下表:
所以DECIMAL(16, 4)
共需要占用8
个字节的存储空间大小,这8个字节由下边3个部分组成:
第1组包含3个十进制位,需要使用2个字节存储。
第2组包含9个十进制位,需要使用4个字节存储。
第3组包含4个十进制位,需要使用2个字节存储。
第1组中包含整数1。 第2组中包含整数234567890。 第3组中包含整数1234。
第1组占用2个字节,整数1对应的二进制数就是(字节之间实际上没有空格,只不过为了大家理解上的方便我们加了一个空格):
二进制看起来太难受,我们还是转换成对应的十六进制看一下:
0x0001
第2组占用4个字节,整数234567890对应的十六进制数就是:
第3组占用2个字节,整数1234对应的十六进制数就是:
DECIMAL(n) = DECIMAL(n, 0)




如果我们想让TIME、DATETIME、TIMESTAMP这几种类型支持小数秒,可以这样写:
类型(小数秒位数)
其中的小数秒位数可以在0、1、2、3、4、5、6中选择
比如DATETIME(0)表示精确到秒,DATETIME(3)表示精确到毫秒,DATETIME(5)表示精确到10微秒。如果你在选择TIME、DATETIME、TIMESTAMP这几种类型的时候添加了对小数秒的支持,那么所需的存储空间需要相应的扩大,保留不同的小数秒位数,那么增加的存储空间大小也不同,如下表:
大家应该发现其中的在没有存储小数秒的情况下,DATETIME类型占用的存储空间从原来的8字节变成了5字节,这是因为MySQL背后做了些努力,使存储格式变得更紧凑了些。
当然,如果你使用的MySQL版本还没到5.6.4,那就不支持小数秒,可以翻上去看原来的存储空间和取值范围。下边我们来详细看一下各种类型。
YEAR
YEAR类型也可以写成YEAR(4),它单纯表示一个年份值,取值范围为1901 ~ 2155,仅仅占用1个字节大小而已。因为可以存储的年份值有限,如果我们想存储更大范围的年份值,可以不使用MySQL自带的YEAR类型,换成SMALLINT(2字节)或者字符串类型啥的都可以。
曾经也有YEAR(2)这种使用2个数字来表示年份的类型,比方说数字99表示1999年。不过在MySQL 5.7.5之后就不再支持这种类型了,因为可能会产生歧义,上篇文章MySQL之DQL语句(四)有提到过,我们稍微了解一下就好了。
DATE、TIME和DATETIME
顾名思义,DATE表示日期,格式是YYYY-MM-DD;TIME表示时间,格式是hh:mm:ss[.uuuuuu]或者hhh:mm:ss[.uuuuuu](有时候要存储的小时值是三位数),DATETIME表示日期+时间,格式是YYYY-MM-DD hh:mm:ss[.uuuuuu]。其中的YYYY、MM、DD、hh、mm、ss、uuuuuu分别表示年、月、日、时、分、秒、小数秒。
需要注意的是,DATETIME中的时间部分表示的是一天内的时间(00:00:00 ~ 23:59:59),而 TIME表示的是一段时间,而且可以表示负值。
TIMESTAMP
1970-01-01 00:00:00注定是一个特殊的时刻,我们把某个时刻距离1970-01-01 00:00:00的秒数称为时间戳。比方说当前时间是2018-01-24 11:39:21,距离1970-01-01 00:00:00的秒数为1516765161,那么2018-01-24 11:39:21这个时刻的时间戳就是1516765161。不过在MySQL 5.6.4之后,时间戳的值也可以加入小数秒。
用时间戳存储时间的好处就是,它展示的值可以随着时区的变化而变化。比方说我们把2018-01-24 11:39:21这个时刻存储到一个TIMESTAMP的列中,那么在中国你看到的时间就是2018-01-24 11:39:21,如果你去了日本,他们哪里的使用的是东京时间,比北京时间早一个小时,所以他们那显示的就是2018-01-24 12:39:21。而如果你用DATETIME存储2018-01-24 11:39:21的话,那不同时区看到的时间值都是一样的。


CHAR(M)
CHAR(M)中的M代表该类型最多可以存储的字符数量,注意,是字符数量,不是字节数量。其中M的取值范围是0~255。如果省略掉M的值,那它的默认值就是1,也就是说CHAR和CHAR(1)是一个意思。CHAR(0)是一种特别的类型,它只能存储空字符串''或者NULL值。再回头看一眼我们的学生基本信息表,如果你觉得学生的姓名不会超过5个字符,你就可以指定这个姓名列的类型为CHAR(5)。
CHAR(M)在不同的字符集下需要的存储空间也是不一样的,我们假设某个字符集编码一个字符最多需要W个字节,那么类型CHAR(M)占用的存储空间大小就是M×W个字节。比方说:
对于采用ascii字符集的CHAR(5)类型来说,ascii字符集编码一个字符最多需要1个字节,也就是M=5、W=1,所以这种情况下该类型占用的存储空间大小就是5×1 = 5个字节。 对于采用gbk字符集的CHAR(5)类型来说,gbk字符集编码一个字符最多需要2个字节,也就是M=5、W=2,所以这种情况下该类型占用的存储空间大小就是5×2 = 10个字节。 对于采用utf8字符集的CHAR(5)类型来说,utf8字符集编码一个字符最多需要3个字节,也就是M=5、W=3,所以这种情况下该类型占用的存储空间大小就是5×3 = 15个字节。
如果我们实际存储的字符串在特定字符集编码下占用的字节数不足M×W,那么剩余的那些存储空间用空格字符(也就是:' ')补齐。比方说表的某个属性的类型是采用ascii字符集的CHAR(5)类型,我们想将字符串'abc'存入使用这个类型的表属性中,其中字符串'abc'在ascii字符集下需要3个字节存储,而采用ascii字符集的CHAR(5)类型又需要5个字节的存储空间,那么剩下的那两个字节的存储空间就会存储空格字符' '的编码。这也就是说:一旦你确定了CHAR(M)类型的M的值,如果M的值很大,而你实际存储的字符串占用字节数又很少,会造成存储空间的浪费。
补充一点,CHAR和VARCHAR虽然分别用于存储定长和变长字符,但对于变长字符集(如GBK、UTF8MB4),其本质是一样的,都是变长,设计时完全可以用VARCHAR替代CHAR, CHAR(1)既可以存储1个'a' 字节,也可以存储4个字节的emoji笑脸表情,因此CHAR本质也是变长的。看图。
VARCHAR(M)
如果你表中的某个列需要存储字符串类型的数据,而且这些字符串长短不一,那么使用CHAR(M)可能会浪费很多存储空间,VARCHAR(M)正是为了解决这个问题而生的。
VARCHAR(M)中的M也是代表该类型最多可以存储的字符数量,理论上的取值范围是1~65535。但是MySQL中还有一个规定,表中某一行包含的所有列中存储的数据大小总共不得超过65535个字节(注意是字节),也就是说VARCHAR(M)类型实际能够容纳的字符数量是小于65535的。
VARCHAR(M)类型占用的存储空间不确定,那系统在读一个VARCHAR(M)类型的数据时怎么知道该数据占用多少个字节呢?答案是:不知道。所以一个VARCHAR(M)类型表示的数据其实是由这么两部分组成:
当M×W < 256时,只需要一个字节来表示占用的字节数。
当M×W >= 256且M×W < 65536时,需要两个字节来表示占用的字节数。
各种TEXT类型
虽然VARCHAR(M)已经可以存储很长的字符串了,可是有时候还是不够咋办?对于很长的字符串,设计MySQL的大叔们给我们提供了TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT四种可以存储大型的字符串的类型。它们也都是变长类型,也就是说这些类型占用的存储空间由实际内容和内容占用的字节长度两部分构成。
因为TINYTEXT最多可以存储2⁸-1个字节,所以内容占用的字节长度用1个字节就可以表示 TEXT最多可以存储2¹⁶-1个字节,所以内容占用的字节长度用2个字节就可以表示。 MEDIUMTEXT最多可以存储2²⁴-1个字节,所以内容占用的字节长度用3个字节就可以表示。 LONGTEXT最多可以存储2³²-1个字节,所以内容占用的字节长度用4个字节就可以表示。
不过之前不是有个规定说某一行包含的所有列中存储的数据大小总和不得超过65535个字节么?这个规定对这些TEXT类型是不起作用的,它们并不在这个规定的限制范围之内。一个表中如果有的属性需要存储特别长的文本的话,就可以考虑使用这几个类型了。
今天看到一个很有意思的问题:“要是字段总是显示text(0)这个是显示错误还是有什么特殊含义?text可以限定字段长度吗?”,之前因为MySQL开发规范中禁止使用TEXT类型,所以也没有遇到过这样的问题,所以做了一个实验。
ENUM类型和SET类型
视角回到我们的学生信息表,性别一列也需要填写字符串,但是比较特殊的一点是,这一列只能填男或者女,填别的字符串就尴尬了!针对这种情况,我们提出了一个叫ENUM的类型,也称为枚举类型,它的格式如下:
它表示在给定的字符串列表里选择一个。比如我们的性别一列可以定义成ENUM('男', '女')类型。这个的意思就是性别一列只能在'男'或者'女'这两个字符串之间选择一个,相当于一个单选框。
它表示可以在给定的字符串列表里选择多个。我们的兴趣一列就可以定义成SET('打球', '画画', '扯犊子', '玩游戏')类型。这个的意思就是兴趣一列可以在给定的这几个字符串中选择一个或多个,相当于一个多选框。
综上所述,ENUM和SET类型都是一种特殊的字符串类型,在从字符串列表中单选或多选元素的时候会用得到它们。


BIT类型
有时候我们有存储单个或者多个比特位的需求,此时就可以用到下边这种类型:
BIT(1)类型仅仅需要存储1个比特位的数据,但是MySQL会为其申请(1+7)/8 = 1个字节。
BIT(5)类型仅仅需要存储5个比特位的数据,但是MySQL会为其申请(5+7)/8 = 1个字节。
BIT(9)类型仅仅需要存储9个比特位的数据,但是MySQL会为其申请(9+7)/8 = 2个字节。
BINARY(M)与VARBINARY(M)
BINARY(M)和VARBINARY(M)对应于我们前边提到的CHAR(M)和VARCHAR(M),都是前者是固定长度的类型,后者是可变长度的类型,只不过BINARY(M)和VARBINARY(M)是用来存放字节的,其中的M代表该类型最多能存放的字节数量,而CHAR(M)和VARCHAR(M)是用来存储字符的,其中的M代表该类型最多能存放的字符数量。
其他的二进制类型
TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB是针对数据量很大的二进制数据提出的,比如图片、音频、压缩文件啥的。它们很像TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT,不过各种BLOB类型是用来存储字节的,而各种TEXT类型是用来存储字符的而已。
小贴士:对于比较大的二进制数据,比方说图片、音频、压缩文件什么的,通常情况下都不直接存储到数据库管理系统中,而是将它们保存到文件系统中,然后在数据库中之存放一个文件路径即可。



end