第3章、MySQL数据类型

标签: MySQL是怎样使用的新版


我们前边说过,MySQL底层其实把数据存储到了表里边,而表又是由行和列组成的,还是拿我们之前说过的学生基本信息表做个例子:

学生基本信息表

学号 姓名 性别 身份证号 学院 专业 入学时间
20180101 杜子腾 158177199901044792 计算机学院 计算机科学与工程 2018-09-01
20180102 杜琦燕 151008199801178529 计算机学院 计算机科学与工程 2018-09-01
20180103 范统 17156319980116959X 计算机学院 软件工程 2018-09-01
20180104 史珍香 141992199701078600 计算机学院 软件工程 2018-09-01
20180105 范剑 181048200008156368 航天学院 飞行器设计 2018-09-01
20180106 朱逸群 197995199801078445 航海学院 电子信息 2018-09-01

表里的一行就代表一个学生的基本信息,这一行中的某一列就代表这个学生基本信息中的一项属性,也就是说学号是学生的一项属性、姓名也是学生的一项属性,其他的列也都是这个学生的属性。但是这些属性都有一定格式,比如说学号必须是整数格式的,入学时间必须是日期格式的,其他的属性都是字符串格式的,不同格式的数据是不能随便乱填的,你把一个日期格式的数据填在了性别里,岂不是闹出了笑话。所以设计MySQL的大叔们针对属性的不同格式定义了不同的数据类型,我们接下来就要详细唠叨MySQL中具体有哪些数据类型。

小贴士: 身份证号由于最后一位可能是X,所以就归为字符串了。

数值类型

整数类型

进制

long long ago,原始人是没有现在的人这么聪明的,只会用十分简单的东西计数。比方说他们只会使用麦秆来统计猎物数量,每收获一个猎物就在麦秆堆里添加一支麦秆,所以如果猎物多的话,麦秆就会累积很多,数都数不过来。后来人们发现不用这么笨,可以把麦秆折叠成不同的形状来代表不同的数量。普通的麦秆就代表1,也就是说每添加一只猎物就多放一只普通麦秆;如果当前已经放了9根普通的麦秆,此时再添加了一只猎物,就用一个心形麦秆来取代之前的9根普通麦秆;假如当前已经有了9根心形麦秆和9根普通麦秆时又添加了一只猎物的话,就用一根矩形麦秆来替代之前所有的麦秆。像这种每逢10个数向前进一位的计数方法就叫做十进制,这样他们就可以用很少的麦秆来表示很大的数字了。

如果在计数的时候每逢8个数就往前进一位就是八进制,每逢9个数就往前进一位就是九进制。生活中常用的进制除了十进制外,还有钟表里用来统计时间的十二进制和六十进制,用在计算机里的二进制和十六进制。 以十进制数字109为例,它表示有1个10²,加0个10¹,加9个10⁰,用数学符号表示就是:

  1. 109 = 1 × 10² + 0 × 10¹ + 9 × 10

这个数字也可以这么表示:

  1. 109 = 1 × 8² + 5 × 8¹ + 5 × 8

也就是从八进制的逢8进一的角度上考虑,这个数可以被表示为155(八进制)。当然这个数也可以这么写:

  1. 109 = 1 × 2 + 1 × 2 + 0 × 2 + 1 × 2³ + 1 × 2² + 0 × 2¹ + 1 × 2

也就是从二进制的逢2进一的角度上考虑,这个数也可以被表示为:1101101(二进制)。又因为计算机中8个比特位代表一个字节,平时都是用若干个字节来表示一个整数,假如用1个字节表示109的话,那效果就是这样:01101101(二进制),假如用两个字节表示十进制数109的话,那效果就是这样:0000000001101101(二进制)。

MySQL的整数类型

很显然,使用的字节数越多,意味着能表示的数值范围就越大,但是也就越耗费存储空间。根据表示一个数占用字节数的不同,MySQL把整数划分成如下所示的类型:

类型 占用的存储空间(单位:字节) 无符号数取值范围 有符号数取值范围 含义
TINYINT 1 0 ~ 2⁸-1 -2⁷ ~ 2⁷-1 非常小的整数
SMALLINT 2 0 ~ 2¹⁶-1 -2¹⁵ ~ 2¹⁵-1 小的整数
MEDIUMINT 3 0 ~ 2²⁴-1 -2²³ ~ 2²³-1 中等大小的整数
INT(别名:INTEGER 4 0 ~ 2³²-1 -2³¹ ~ 2³¹-1 标准的整数
BIGINT 8 0 ~ 2⁶⁴-1 -2⁶³ ~ 2⁶³-1 大整数

TINYINT为例,用1个字节,也就是8个位表示有符号数的话,就是既可以表示正数,也可以表示负数的话,需要有一个比特位表示正负号。但是如果表示无符号数的话,也就是只表示非负数的话,就不需要表示正负号,这是有符号数无符号数的区别。具体每个类型的取值范围是如何计算出来的我们这就不唠叨了,可以找一本计算机基础的书看看。

小贴士: 如果觉得讲解计算机基础的书籍太过晦涩,没关系,等我~

浮点数类型

用二进制表示十进制小数

浮点数是用来表示小数的,我们平时用的十进制小数也可以被转换成二进制后被计算机存储。比如9.875,这个小数可以被表示成这样:

  1. 9.875 = 8 + 1 + 0.5 + 0.25 + 0.125 = 1 × 2³ + 1 × 2 + 1 × 2⁻¹ + 1 × 2⁻² + 1 × 2⁻³

也就是说,如果十进制小数9.875转换成二进制小数的话就是:1001.111。为了在计算机里存储这种二进制小数,我们统一把它们表示成a × 2ⁿ的科学计数法的形式,其中1≤|a|<2,比如1001.111可以被表示成1.001111 × 2³,我们把小数点之后的001111称为尾数,把中的3称为指数,然后只需要在计算机中的比特位中表示出尾数指数就行了。另外,小数也有正负之分,我们还需要单独的部分来表示小数的正负号。综上所述,表示一个浮点数需要下边几个部分:

  • 符号部分,占用1个比特位即可。

  • 指数部分,视具体浮点数格式而定。

  • 尾数部分,视具体浮点数格式而定。

MySQL的浮点数类型

很显然,我们表示一个浮点数使用的字节数越多,表示尾数指数的范围就越大,也就是说可以表示的小数范围就越大,设计MySQL的大叔根据表示一个小数需要的不同字节数定义了如下的两种浮点数类型:

类型 占用的存储空间(单位:字节) 绝对值最小非0值 绝对值最大非0值 含义
FLOAT 4 ±1.175494351E-38 ±3.402823466E+38 单精度浮点数
DOUBLE 8 ±2.2250738585072014E-308 ±1.7976931348623157E+308 双精度浮点数

以单精度浮点数类型FLOAT类型为例,它占用的4个字节的各个组成部分如下图所示:

image_1df5js9982v3qai1tne14db17b89.png-78kB

另外需要注意的是,虽然有的十进制小数,比如1.875可以被很容易的转换成二进制数1.111,但是更多的小数是无法直接转换成二进制的,比如说0.3,它转换成的二进制小数就是一个无限小数,但是我们现在只能用4个字节或者8个字节来表示这个小数,所以只能进行一些舍入来近似的表示,所以我们说计算机的浮点数表示有时是不精确的。

设置最大位数和小数位数

在定义浮点数类型时,还可以在FLOAT或者DOUBLE后边跟上两个参数,就像这样:

  1. FLOAT(M, D)
  2. DOUBLE(M, D)

对于我们用户而言,使用的都是十进制小数。如果我们事先知道表中的某个列要存储的小数在一定范围内,我们可以使用FLOAT(M, D)或者DOUBLE(M, D)来限制可以存储到本列中的小数范围。其中:

  • M表示该小数最多需要的十进制有效数字个数。

    注意是有效数字个数,比方说对于小数-2.3来说有效数字个数就是2,对于小数0.9来说有效数字个数就是1

  • D表示该小数的小数点后的十进制数字个数。

    这个好理解,小数点后有几个十进制数字,D的值就是什么。

举个例子看一下,设置了MD的单精度浮点数的取值范围的变化:

类型 取值范围
FLOAT(4, 1) -999.9~999.9
FLOAT(5, 1) -9999.9~9999.9
FLOAT(6, 1) -99999.9~99999.9
FLOAT(4, 0) -9999~9999
FLOAT(4, 1) -999.9~999.9
FLOAT(4, 2) -99.99~99.99

可以看到,在D相同的情况下,M越大,该类型的取值范围越大;在M相同的情况下,D越大,该类型的取值范围越小。当然,MD的取值也不是无限大的,M的取值范围是1~255D的取值范围是0~30,而且D的值必须不大于MMD都是可选的,如果我们省略了它们,那它们的值按照机器支持的最大值来存储。

定点数类型

正因为用浮点数表示小数可能会有不精确的情况,在一些情况下我们必须保证小数是精确的,所以设计MySQL的大叔们提出一种称之为定点数的数据类型,它也是存储小数的一种方式:

类型 占用的存储空间(单位:字节) 取值范围
DECIMAL(M, D) 取决于M和D 取决于M和D

此处的MD的含义与浮点数中的含义一样。MD对取值范围的影响我们之前在唠叨浮点数的时候已经介绍过了,但是我们又说单精度浮点数类型FLOAT(M, D)占用的字节数一直都是4字节,双精度浮点数DOUBLE(M, D)占用的字节数一直都是8字节,它们占用的存储空间大小并不随着M和D的值的变动而变动,为啥到了这个所谓的定点数类型DECIMAL(M, D)中,它占用的存储空间大小就和MD的取值有关了呢?哈哈,回答这个问题还得且听我细细道来。

我们说定点数是一种精确的小数,为了达到精确的目的我们就不能把它转换成二进制小数之后再存储(因为有很多十进制小数转为二进制小数后需要进行舍入操作,导致二进制小数表示的数值是不精确的)。其实转念一想,所谓的小数只是把两个十进制整数用小数点分割开来而已,我们只要把小数点左右的两个十进制整数给存储起来,那不就是精确的了么。比方说对于十进制小数2.38来说,我们可以把这个小数的小数点左右的两个整数,也就是238分别保存起来,那么不就相当于保存了一个精确的小数么,这波操作是不是很6。

当然事情并没有这么简单,对于给定MD值的DECIMAL(M, D)类型,比如DEMCIMAL(16, 4)来说:

  • 首先确定小数点左边的整数最多需要存储的十进制位数是12位,小数点右边的整数需要存储的十进制位数是4位,如图所示:

    image_1df8aio7c1lph9hn1c6l12jnee823.png-62.7kB

  • 从小数点位置出发,每个整数每隔9个十进制位划分为1组,效果就是这样:

    image_1dljnip0v1jif13rpepda9ttju9.png-35.5kB

    从图中可以看出,如果不足9个十进制位,也会被划分成一组。

  • 针对每个组中的十进制数字,将其转换为二进制数字进行存储,根据组中包含的十进制数字位数不同,所需的存储空间大小也不同,具体见下表:

    | 组中包含的十进制位数 | 占用存储空间大小(单位:字节) | | —- | —- | | 1或2 | 1 | | 3或4 | 2 | | 5或6 | 3 | | 7或8或9 | 4 |

    所以DECIMAL(16, 4)共需要占用8个字节的存储空间大小,这8个字节由下边3个部分组成:

    • 第1组包含3个十进制位,需要使用2个字节存储。
    • 第2组包含9个十进制位,需要使用4个字节存储。
    • 第3组包含4个十进制位,需要使用2个字节存储。
  • 将转换完成的比特位序列的最高位设置为1。

这些步骤看的有一丢丢懵逼吧,别着急,举个例子就都清楚了。比方说我们使用定点数类型DECIMAL(16, 4)来存储十进制小数1234567890.1234,这个小数会被划分成3个部分:

  1. 1 234567890 1234

也就是:

  • 第1组中包含整数1
  • 第2组中包含整数234567890
  • 第3组中包含整数1234

然后将每一组中的十进制数字转换成对应的二进制数字:

  • 第1组占用2个字节,整数1对应的二进制数就是(字节之间实际上没有空格,只不过为了大家理解上的方便我们加了一个空格):

    1. 00000000 00000001
  1. 二进制看起来太难受,我们还是转换成对应的十六进制看一下:
  2. 0x0001
  • 第2组占用4个字节,整数234567890对应的十六进制数就是:

    1. 0x0DFB38D2
  • 第3组占用2个字节,整数1234对应的十六进制数就是:

    1. 0x04D2

所以将这些十六进制数字连起来之后就是:

  1. 0x00010DFB38D204D2

最后还要将这个结果的最高位设置为1,所以最终十进制小数1234567890.1234使用定点数类型DECIMAL(16, 4)存储时共占用8个字节,具体内容为:

  1. 0x80010DFB38D204D2

有的同学会问,如果我们想使用定点数类型DECIMAL(16, 4)存储一个负数怎么办,比方说-1234567890.1234,这时只需要将0x80010DFB38D204D2中的每一个比特位都执行一个取反操作就好,也就是得到下边这个结果:

  1. 0x7FFEF204C72DFB2D

从上边的叙述中我们可以知道,对于DECIMAL(M, D)类型来说,给定的MD的值不同,所需的存储空间大小也不同。可以看到,与浮点数相比,定点数需要更多的空间来存储数据,所以如果不是在某些需要存储精确小数的场景下,一般的小数用浮点数表示就足够了。

对于定点数类型DECIMAL(M, D)来说,MD都是可选的,默认的M的值是10,默认的D的值是0,也就是说下列等式是成立的:

  1. DECIMAL = DECIMAL(10) = DECIMAL(10, 0)
  2. DECIMAL(n) = DECIMAL(n, 0)

另外M的范围是1~65D的范围是0~30,且D的值不能超过M

无符号数值类型的表示

对于数值类型,包括整数、浮点数和定点数,有些情况下我们只需要用到无符号数(就是非负数)。MySQL给我们提供了一个表示无符号数值类型的方式,就是在原数值类型后加一个单词UNSIGNED

  1. 数值类型 UNSIGNED

大家可以把它当成一种新类型对待,比如INT UNSIGNED就表示无符号整数,FLOAT UNSIGNED表示无符号浮点数,DECIMAL UNSIGNED表示无符号定点数。

小贴士: 在使用的存储空间大小相同的情况下,无符号整数可以表示的正整数范围比有符号整数能表示的正整数范围大一倍。不过受浮点数和定点数具体的存储格式影响,无符号浮点数和定点数并不能提升正数的表示范围。

日期和时间类型

我们有很多场景需要表示时间或日期,比如学生基本信息中的入学时间就需要用日期的格式保存。MySQL为我们提供了多种关于时间和日期的类型,各种类型能表示的范围如下:

类型 存储空间要求 取值范围 含义
YEAR 1字节 1901~2155 年份值
DATE 3字节 ‘1000-01-01’ ~ ‘9999-12-31’ 日期值
TIME 3字节 ‘-838:59:59’ ~ ‘838:59:59’ 时间值
DATETIME 8字节 ‘1000-01-01 00:00:00’ ~ ‘9999-12-31 23:59:59’ 日期加时间值
TIMESTAMP 4字节 ‘1970-01-01 00:00:01’ ~ ‘2038-01-19 03:14:07’ 时间戳

MySQL5.6.4这个版本之后,TIMEDATETIMETIMESTAMP这几种类型添加了对毫秒、微秒的支持。由于毫秒、微秒都不到1秒,所以也被称为小数秒MySQL最多支持6位小数秒的精度,各个位代表的意思如下:

image_1dfalcttv4i7j3ulpais51q909.png-40.8kB

如果我们想让TIMEDATETIMETIMESTAMP这几种类型支持小数秒,可以这样写:

  1. 类型(小数秒位数)
  2. 其中的小数秒位数可以在0123456中选择

比如DATETIME(0)表示精确到秒,DATETIME(3)表示精确到毫秒,DATETIME(5)表示精确到10微秒。如果你在选择TIMEDATETIMETIMESTAMP这几种类型的时候添加了对小数秒的支持,那么所需的存储空间需要相应的扩大,保留不同的小数秒位数,那么增加的存储空间大小也不同,如下表:

保留的小数秒位数 额外需要的存储空间要
0 0字节
1或2 1字节
3或4 2字节
5或6 3字节

也就是说如果你选择使用DATETIME(1),那么需要的存储空间就是在DATETIME的空间上再加上小数秒需要的空间,就是8 + 1 = 9个字节,类似的,DATETIME(3)就需要8 + 2 = 10个字节。所以,MySQL5.6.4这个版本之后的各个类型需要的存储空间和取值范围就如下:

类型 存储空间要求 取值范围 含义
YEAR 1字节 1901~2155 年份值
DATE 3字节 ‘1000-01-01’ ~ ‘9999-12-31’ 日期值
TIME 3字节+小数秒的存储空间 ‘-838:59:59[.000000]‘ ~ ‘838:59:59[.000000]‘ 时间值
DATETIME 5字节+小数秒的存储空间 ‘1000-01-01 00:00:00[.000000]‘ ~ ‘9999-12-31 23:59:59’[.999999] 日期加时间值
TIMESTAMP 4字节+小数秒的存储空间 ‘1970-01-01 00:00:01[.000000]‘ ~ ‘2038-01-19 03:14:07’[.999999] 时间戳

大家应该发现其中的在没有存储小数秒的情况下,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之后就不再支持这种类型了,我们稍微了解一下就好了。

DATE、TIME和DATETIME

顾名思义,DATE表示日期,格式是YYYY-MM-DDTIME表示时间,格式是hh:mm:ss[.uuuuuu]或者hhh:mm:ss[.uuuuuu](有时候要存储的小时值是三位数),DATETIME表示日期+时间,格式是YYYY-MM-DD hh:mm:ss[.uuuuuu]。其中的YYYYMMDDhhmmssuuuuuu分别表示年、月、日、时、分、秒、小数秒。

需要注意的是,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。不过在MySQL5.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的话,那不同时区看到的时间值都是一样的。

字符串类型

字符和字符串

字符可以大致分为两种,一种叫可见字符,一种叫不可见字符。顾名思义,可见字符就是打印出来后能看见的字符。比如'a''b''我''。' … 这样的人眼能看见的单个的国家文字、标点符号、图形符号、数字等这样的东东,我们就叫做一个可见字符不可见字符也好理解,就是打印机或者在黑框框里打印字符的时候有时候需要换行,打个制表符啥的,或者在输出某个字符的时候就发出地一声,这种我们看不到,只是为了控制输出效果的字符叫做不可见字符字符串就是把字符连起来的样子,比如'abc',就是由'a''b''c'三个字符连起来的一个字符串,下边列举了4个字符串的例子:

  1. '我喜欢你'
  2. 'me, too'
  3. 'give me a hug'
  4. '么么哒'

字符编码简介

在具体分析MySQL中各个字符串类型之前,我们一定要先搞明白字符和字节的区别。字符是面向人的概念,字节是面向计算机的概念。如果你想在计算机中表示字符,那就需要将该字符与一个特定的字节序列对应起来,这个映射过程称之为编码。不幸的是,这种映射关系并不是唯一的,不同的人制作了不同的编码方案,根据表示一个字符使用的字节数量是不是固定的,编码方案可以分为下边两种:

  • 固定长度的编码方案

    表示不同的字符所需要的字节数量是相同的。比方说ASCII编码方案采用1个字节来编码一个字符,ucs2采用2个字节来编码一个字符。

  • 变长的编码方案

    表示不同的字符所需要的字节数量是不同的。比方说utf8编码方案采用1~3个字节来编码一个字符,gb2312采用1~2个字节来编码一个字符。

对于不同的字符编码方案来说,同一个字符可能被编码成不同的字节序列。比如同样一个字符:,在utf8gb2312这两种编码方案下被映射成如下的字节序列:

  • utf8编码方案

    字符'我'被编码成:

    1. 111001101000100010010001
  1. 共占用3个字节,用十六进制表示就是:`0xE68891`
  • gb2312编码方案

    字符'我'被编码成:

    1. 1100111011010010
  1. 共占用2个字节,用十六进制表示就是:`0xCED2`

小贴士: 注:十六进制前边的0x是前缀,表示后边的是16进制数据。

另外,设计MySQL的大叔似乎对编码方案字符集这两个概念并没做什么区分,也就是说我们之后所讲的utf8字符集指的就是utf8编码方案,gb2312字符集指的也就是gb2312编码方案。

小贴士: 正宗的utf8字符集是使用1~4个字节来编码一个字符的,不过MySQL中对utf8字符集做了阉割,编码一个字符最多使用3个字节。如果我们之后有存储使用4个字节来编码的字符的情景,可以使用一种称之为utf8mb4的字符集,它才是正宗的utf8字符集。

MySQL的字符串类型

现在我们可以看一下MySQL中提供的各种字符串类型(注:其中M代表该数据类型最多能存储的字符数量,L代表我们实际向该类型的属性中存储的字符串在特定字符集下所占的字节数,W代表在该特定字符集下,编码一个字符最多需要的字节数):

类型 最大长度 存储空间要求 含义
CHAR(M) M个字符 M×W个字节 固定长度的字符串
VARCHAR(M) M个字符 L+1 或 L+2 个字节 可变长度的字符串
TINYTEXT 2⁸-1 个字节 L+1个字节 非常小型的字符串
TEXT 2¹⁶-1 个字节 L+2 个字节 小型的字符串
MEDIUMTEXT 2²⁴-1 个字节 L+3个字节 中等大小的字符串
LONGTEXT 2³²-1 个字节 L+4个字节 大型的字符串

当然,就画这么个表格大家一准儿有些懵,我们接下来看一下各种字符串类型的细节。

CHAR(M)

CHAR(M)中的M代表该类型最多可以存储的字符数量,注意,是字符数量,不是字节数量。其中M的取值范围是0~255。如果省略掉M的值,那它的默认值就是1,也就是说CHARCHAR(1)是一个意思。CHAR(0)是一种特别的类型,它只能存储空字符串''或者NULL值(我们后边会详细介绍啥是个NULL)。再回头看一眼我们的学生基本信息表,如果你觉得学生的姓名不会超过5个字符,你就可以指定这个姓名列的类型为CHAR(5)

CHAR(M)在不同的字符集下需要的存储空间也是不一样的,我们假设某个字符集编码一个字符最多需要W个字节,那么类型CHAR(M)占用的存储空间大小就是M×W个字节。比方说:

  • 对于采用ascii字符集的CHAR(5)类型来说,ascii字符集编码一个字符最多需要1个字节,也就是M=5W=1,所以这种情况下该类型占用的存储空间大小就是5×1 = 5个字节。

  • 对于采用gbk字符集的CHAR(5)类型来说,gbk字符集编码一个字符最多需要2个字节,也就是M=5W=2,所以这种情况下该类型占用的存储空间大小就是5×2 = 10个字节。

  • 对于采用utf8字符集的CHAR(5)类型来说,utf8字符集编码一个字符最多需要3个字节,也就是M=5W=3,所以这种情况下该类型占用的存储空间大小就是5×3 = 15个字节。

如果我们实际存储的字符串在特定字符集编码下占用的字节数不足M×W,那么剩余的那些存储空间用空格字符(也就是:' ')补齐。比方说表的某个属性的类型是采用ascii字符集的CHAR(5)类型,我们想将字符串'abc'存入使用这个类型的表属性中,其中字符串'abc'ascii字符集下需要3个字节存储,而采用ascii字符集的CHAR(5)类型又需要5个字节的存储空间,那么剩下的那两个字节的存储空间就会存储空格字符' '的编码。这也就是说:一旦你确定了CHAR(M)类型的M的值,如果M的值很大,而你实际存储的字符串占用字节数又很少,会造成存储空间的浪费。

小贴士: 字符’a’在ascii字符集下被编码为0x61,字符’b’在ascii字符集下被编码为0x62,字符’c’在ascii字符集下被编码为0x63,空格字符被编码为0x20,所以将字符串’abc’存入采用ascii字符集的CHAR(5)类型的表属性中时,实际存储的字节序列就是:0x6162632020。

VARCHAR(M)

如果你表中的某个列需要存储字符串类型的数据,而且这些字符串长短不一,那么使用CHAR(M)可能会浪费很多存储空间,VARCHAR(M)正是为了解决这个问题而生的。

VARCHAR(M)中的M也是代表该类型最多可以存储的字符数量,理论上的取值范围是1~65535。但是MySQL中还有一个规定,表中某一行包含的所有列中存储的数据大小总共不得超过65535个字节(注意是字节),也就是说VARCHAR(M)类型实际能够容纳的字符数量是小于65535的。

VARCHAR(M)类型占用的存储空间不确定,那系统在读一个VARCHAR(M)类型的数据时怎么知道该数据占用多少个字节呢?答案是:不知道。所以一个VARCHAR(M)类型表示的数据其实是由这么两部分组成:

  1. 真正的字符串内容。

    假设真正的字符串在特定字符集编码后占用的字节数为L

  2. 占用字节数。

    假设VARCHAR(M)类型采用的字符集编码一个字符最多需要W个字节,那么:

    • M×W < 256时,只需要一个字节来表示占用的字节数。

    • M×W >= 256M×W < 65536时,需要两个字节来表示占用的字节数。

  1. > 小贴士: 一个字节占用8个比特位,能表示的最大无符号数就是255,两个字节占用16个比特位,能表示的最大无符号数就是65535

我们还用学生的姓名属性做例子,假设我们给姓名列定义的类型为采用utf8字符集的VARCHAR(5),也就是说M = 5W = 3,所以M × W= 5×3 = 15,而15 < 256,所以我们只需要一个字节来表示真实数据占用的字节长度就好了。对于'杜子腾''范统'这两个字符串来说,它们在utf8字符集下可以被编码成如下的样子(二进制太长了,用16进制表示):

  1. '杜子腾'0xE69D9CE5AD90E885BE (共9个字节)
  2. '范统'0xE88C83E7BB9F (共6个字节)

那么这两个字符串的实际存储示意图就是这样:

image_1dfis1kur8ol1vm1ear12aar6a1j.png-86.1kB

而如果我们给姓名列定义的类型为采用utf8字符集的VARCHAR(100),也就是说M = 100W = 3,所以M × W= 100×3 = 300,而300 > 256,所以我们需要2个字节来表示真实数据占用的字节长度,此时'杜子腾''范统'这两个字符串的实际存储示意图就是这样:

image_1dfis13nm1rrn1ofl17ck1chf1mvp16.png-86.7kB

从上边的示例中可以看出,VARCHAR(M)类型占用的存储空间大小随着实际存储的内容变化而变化,假设实际存储的内容占用的字节长度为L,那么整个VARCHAR(M)类型占用的存储空间大小就是L+1或者L+2个字节。所以我们说VARCHAR(M)是一种可变长度的字符串类型。

各种TEXT类型

虽然VARCHAR(M)已经可以存储很长的字符串了,可是有时候还是不够咋办?对于很长的字符串,设计MySQL的大叔们给我们提供了TINYTEXTTEXTMEDIUMTEXTLONGTEXT四种可以存储大型的字符串的类型。它们也都是变长类型,也就是说这些类型占用的存储空间由实际内容和内容占用的字节长度两部分构成。

  • 因为TINYTEXT最多可以存储2⁸-1个字节,所以内容占用的字节长度用1个字节就可以表示

  • TEXT最多可以存储2¹⁶-1个字节,所以内容占用的字节长度用2个字节就可以表示。

  • MEDIUMTEXT最多可以存储2²⁴-1个字节,所以内容占用的字节长度用3个字节就可以表示。

  • LONGTEXT最多可以存储2³²-1个字节,所以内容占用的字节长度用4个字节就可以表示。

不过之前不是有个规定说某一行包含的所有列中存储的数据大小总和不得超过65535个字节么?这个规定对这些TEXT类型是不起作用的,它们并不在这个规定的限制范围之内。一个表中如果有的属性需要存储特别长的文本的话,就可以考虑使用这几个类型了。

ENUM类型和SET类型

视角回到我们的学生信息表,性别一列也需要填写字符串,但是比较特殊的一点是,这一列只能填或者,填别的字符串就尴尬了!针对这种情况,我们提出了一个叫ENUM的类型,也称为枚举类型,它的格式如下:

  1. ENUM('str1', 'str2', 'str3' ⋯)

它表示在给定的字符串列表里选择一个。比如我们的性别一列可以定义成ENUM('男', '女')类型。这个的意思就是性别一列只能在'男'或者'女'这两个字符串之间选择一个,相当于一个单选框~

有的时候某一列的值可以在给定的字符串列表中挑选多个,假设学生的基本信息加了一列兴趣属性,这个属性的值可以从给定的兴趣列表中挑选多个,那我们可以使用SET类型,它的格式如下:

  1. SET('str1', 'str2', 'str3' ⋯)

它表示可以在给定的字符串列表里选择多个。我们的兴趣一列就可以定义成SET('打球', '画画', '扯犊子', '玩游戏')类型。这个的意思就是兴趣一列可以在给定的这几个字符串中选择一个或多个,相当于一个多选框~效果就像这样:

学号 姓名 ··· 兴趣
20180101 杜子腾 ··· ‘打球’, ‘画画’
20180102 杜琦燕 ··· ‘扯犊子’
20180103 范统 ··· ‘扯犊子’, ‘玩游戏’
20180104 史珍香 ··· ‘画画’, ‘扯犊子’, ‘玩游戏’

综上所述,ENUM和SET类型都是一种特殊的字符串类型,在从字符串列表中单选或多选元素的时候会用得到它们。

二进制类型

BIT类型

有时候我们有存储单个或者多个比特位的需求,此时就可以用到下边这种类型:

类型 字节数 含义
BIT(M) 近似为(M+7)/8 存储M个比特位的值

其中M的取值范围为1~64,而且M可以省略,它的默认值为1,也就是说BIT(1)BIT的意思是一样的。

MySQL是以字节为单位存储数据的,一个字节拥有8个比特位。如果我们想存储的比特位个数不足整数个字节,那么MySQL会偷偷的填充满,比方说:

  • 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代表该类型最多能存放的字符数量。

其他的二进制类型

TINYBLOBBLOBMEDIUMBLOBLONGBLOB是针对数据量很大的二进制数据提出的,比如图片、音频、压缩文件啥的。它们很像TINYTEXTTEXTMEDIUMTEXTLONGTEXT,不过各种BLOB类型是用来存储字节的,而各种TEXT类型是用来存储字符的而已。

小贴士: 对于比较大的二进制数据,比方说图片、音频、压缩文件什么的,通常情况下都不直接存储到数据库管理系统中,而是将它们保存到文件系统中,然后在数据库中之存放一个文件路径即可。

注意

小贴士: MySQL其实可以大致被分为server层和存储引擎层,server层用来做一些通用的逻辑,存储引擎层负责具体的数据读取和存储,针对不同的使用场景设计了许多种不同的存储引擎。各种数据类型在server层的格式是一致的,本文对各种数据类型占用存储空间的分析也是基于server层的,不同的存储引擎针对不同的数据类型可能有其特定的实现,我们就不单独唠叨了。在《MySQL是怎样运行的:从根儿上理解MySQL》这本书中介绍了InnoDB存储引擎对于VARCHAR和CHAR格式的具体实现,有兴趣的小伙伴可以到该书中查看。 当然,这段话是给有经验的小伙伴看的,如果大家压根儿没听说过server层、存储引擎的概念,请自动忽略这段话,请自动忽略这段话,请自动忽略这段话!