[MySQL 之二] 数据类型

1、数值类型

(1)整型

整数类型 字节 最小值 最大值
TINYINT 1 有符号-27
无符号 0
有符号 27-1
无符号 28-1
SMALLINT 2 有符号-215
无符号 0
有符号 215-1
无符号 216-1
MEDIUMINT 3 有符号-223
无符号 0
有符号 223-1
无符号 224
INT、INTEGER 4 有符号-231
无符号 0
有符号 231-1
无符号 232
BIGINT 8 有符号-263
无符号 0
有符号 263-1
无符号 264

在整数类型中,应根据取值范围合理选取占最小存储空间的类型,DDL 定义数据类型时,MySQL 在类型名称后面的小括号指定显式宽度(而不是十进制数的位数),比如 **int(5) **不代表能存储的最大数字是 99999,而是表示当数值宽度小于 5 位时在数字前面填满宽度,一般配合 zerofill 使用,如果不显式指定宽度即 int,则默认为 int(11) ,如下所示:

往表中插入数据 (100000, 100000),证明 int(5) 的储值范围跟 int(11) 是一致的。

配合 zerofill 使用,在数值前面用 "0" 填充了剩余的宽度。

通过 signedunsigned 可以显式整型字段是否为带符号整数,如果使用了 zerofill,则 MySQL 自动为该列添加 unsigned 属性。

PS. 一般我们会为数据表定义一个整型字段,并设置其为自增(AUTO_INCREMENT),并作为表的主键(主键默认是唯一索引)。


(2)浮点数和定点数

符点数类型 字节 最小值 最大值
FLOAT 4 ±1.175494351E-38 ±3.402823466E+38
DOUBLE 8 ±2.2250738585072014E-308 ±1.7976931348623157E+308
定点数类型 字节 描述
DEC(M,D)
DECIMAL(M,D)
M+2 最大取值范围与 DOUBLE 相同,给定 DECIMAL 的有效取值范围由 M 和 D 决定

  浮点数在计算机中的存储机制决定了它不是一个具体的值,而是一个可能随着精确度的变化而变化的值,关于浮点数的存储机制原理,可参考:浮点数在计算机中存储方式。

  浮点数和定点数都可以用类型名称后加 "(M,D)" 的方式来进行标识,表示该值一共显式 M 位数字(整数位+小数位),其中 D 位位于小数点后面,M 和 D 又称为精度和标度。精度和标度对于浮点数来说,如果要存储的数据超出这个范围,依然可以正常存储,类似于 int(5);但是对定点数来说,存储超出精度标度表示范围的数字,会发生截断或报错(具体表现由 SQLMode 决定)。

【测试】

插入精度标度表示范围内的定点数和浮点数

插入精度标度表示范围外的定点数和浮点数,id1、id2 由于标度的限制,舍去了最后一位,数据变成了 1.23,而 id3 被截断,系统出现警告!

修改表结构,舍弃显式指定精度标度,可以看出定点数默认为 decimal(10,0)

而浮点数如果不指定精度标度,则会按照实际精度值显示;如果有精度和标度,则会自动将四舍五入的结果插入,系统不会报错。


(3)比特类型

位类型 字节 最小值 最大值
BIT(M) 1~8 BIT(1) BIT(64)

  对于 BIT(位)类型,用于存放位字段值,BIT(M) 可以用来存放多位二进制数,M 范围为 1~64,如果不写,则默认为 1 位。对于位字段,直接使用 SELECT 命令将不会看到结果,可以使用 bin()(显示为二进制格式)或者 hex()(显示为十六进制格式)函数进行读取。

  如果位数小于实际定义的位数,则插入失败,将字段定义修改为 bit(2) 后才插入成功



2、日期时间类型

日期和时间类型 字节 最小值 最大值
DATE 4 1000-01-01 9999-12-31
DATETIME 8 1000-01-01 00:00:00 9999-12-31 23:59:59
TIMESTAMP 4 19700101080001 2038 年的某个时刻
TIME 3 -823:59:59 823:59:59
YEAR 1 1901 2155
  • 表示年月日,使用 DATE
  • 表示年月日时分秒,使用 DATETIMETIMESTAMP,一般使用 DATETIME,TIMESTAMP 表示的时间范围小,但是能配合时区使用。
  • 表示时分秒,使用 TIME
  • 表示年份,使用 YEAR,有 2 位和 4 位两种格式,默认是 4 位格式,允许的值是 1901~2155 和 0000;在 2 位格式中,允许的值是 70~69 表示从 1970~2069 年。(从 MySQL 5.5.27 开始,2 位格式已不再被支持)

【测试】

(1)DATE、TIME、DATETIME

测试 DATE、TIME、DATETIME 类型的使用

PS. 有时候,记录一些时间需要精确到毫秒,那么就可以定义类型 datetime(3)(3 表示小数点后三位)。


(2)TIMESTAMP

测试 TIMESTAMP 类型,可以看到使用 timestamp 类型时,默认时间为当前系统时间(Default: CURRENT_TIMESTAMP),并且在数据更新时自动更新时间戳(Extra: on update CURRENT_TIMESTAMP)。

根据测试情况,确实做到了默认时间和自动更新时间

Mysql 只给第一个 TIMESTAMP 字段设置默认值为系统日期,如果有第二个 TIMESTAMP 类型,默认值为 0

MySQL 5.6 之前,可以修改 time2 的默认值为其他常量日期,但不能再修改为 current_timestamp,因为规定了 TIMESTAMP 类型字段只能有一列的默认值为 current_timestamp;MySQL 5.6 版本之后,无此限制。

是否为表中第一个 TIMESTAMP 类型的字段设置默认值,是由 explicit_defaults_for_timestamp 参数决定的,默认为 off,若设置为 on,则默认值、not null 和 on update CURRENT_TIMESTAMP 属性都不会自动设置。

TIMESTAMP 类型具有时区相关的特点:插入日期时,会先转换为本地时区后存放;而从数据库里面取出时,同样需要将日期转换为本地时区后显示。

【时区相关性 - 演示】

① 先创建数据表

② 查看当前时区,显示为 SYSTEM 则与当前主机的时区一致,即为东八区(+8:00

③ 插入一行数据

④ 修改时区为东九区,再次查看数据

可以看到 time1 存储的 "2021-06-21 14:07:02" 已经变成了 "2021-06-21 15:07:02",因为东九区的时间要比东八区快一个小时,而未设置默认值为 CURRENT_TIMESTAMP 的 time2 字段存储的时间戳值保持不变。


(3)YEAR

YEAR 类型用来表示年份,只需要记录年份时,用 YEAR 比 DATE 更节省空间,范围是 1901~2155。



3、字符串类型

字符串类型 字节 描述及存储需求
CHAR(M) M M 为 0~255 之间的整数
VARCHAR(M) M 为 0~65535 之间的整数,值的长度为+1 个字节
TINYBLOB 允许长度 0~255 字节,值的长度+1 个字节
BLOB 允许长度 0~65535 字节,值的长度+2 个字节
MEDIUMBLOB 允许长度 0~167772150 字节,值的长度+3 个字节
LONGBLOB 允许长度 0~4294967295 字节,值的长度+4 个字节
TINYTEXT 允许长度 0~255 字节,值的长度+2 个字节
TEXT 允许长度 0~65535 之间的整数,值的长度为+2 个字节
MEDIUMTEXT 允许长度 0~167772150 之间的整数,值的长度为+3 个字节
LONGTEXT 允许长度 0~4294967295 字节,值的长度+4 个字节
VARBINARY(M) 允许长度 0~M 个字节的变长字节字符串,值的长度+1 个字节
BINARY(M) M 允许长度 0~M 个字节的定长字节字符串


(1)CHAR 和 VARCHAR

① 区别

  • char 列的长度固定为创建表时声明的长度,即定义了多长系统就分配多大的空间,长度可以为 0~255 的任何值。
  • varchar 列中的值为可变长字符串,即用多少分配多少,如果值大小有变,则重新分配空间,长度可以为 0~65535 之间的值。
  • char 列会删除尾部的空格,因为存储数据时多余的空间它会用空格来填充,所以尾部带空格的数据不适合用 char 存储;varchar 则保留这些空格。

利用字符串连接函数,可以看得更清楚

很明显,char 列删除了尾部空格。


② 使用原则

  • char 是固定长度的,所以处理速度更快,缺点是浪费存储空间,并且需要特别注意行尾空格,对那些长度变化不大并且对查询速度有较高要求的数据可以考虑使用。

  • varchar 长度可变,但仍要合理按需定义长度,定义一个远超实际需求长度的 VARCHAR 字段可能影响应用程序的效率。

    不同存储引擎的使用原则:

  • MyISAM 存储引擎:建议使用固定长度的数据列代替可变长度的数据列。

  • MEMORY 存储引擎:目前都使用固定长度的数据行存储,因此无论使用 CHAR 或 VARCHAR 列都没有关系,两者都是作为 CHAR 类型处理。

  • InnoDB 存储引擎:建议使用 VARCHAR 类型,对于 InnoDB 数据表,内部的行存储格式没有区分固定长度和可变长度列(所有数据行都使用指向数据列值的头指针),因此在本质上,使用固定长度的 CHAR 列不一定比使用可变长度 VARCHAR 列性能要好。因此,主要的性能因素是数据行使用的存储总量。由于 CHAR 平均占用的空间多于 VARCHAR,因此使用 VARCHAR 来最小化需要处理的数据行的存储总量和磁盘 I/O 是较好的。


(2)BLOB 和 TEXT

  当需要保存较大的数据时,一般使用 BLOB 或 TEXT。

① 区别

  • BLOB 保存二进制数据,比如照片、语音等。
  • TEXT 保存字符数据,比如文章、日记等。


② 存在问题

  • BLOB 和 TEXT 能存储比较大的数据,因此执行大量的删除操作时,会在数据表中留下很大的空洞,以后填入这些 “空洞” 的记录在插入的性能上会有影响。

  • 存储引擎每次 I/O 的数据块大小是有限制的,比如 InnoDB 每次读取的一页为 16K,如果 text、blob 存储的数据超过 16K,则数据存储跨越多页,读取一行数据时需要翻页,即多次 I/O,在高并发环境下性能表现不佳。

【演示】

a. 创建一个带 text 类型的数据表

b. 插入大量的数据

c. 查看数据文件大小,近 40w 数据达到 188M

d. 删除 id 为 "1" 的数据,从理论上说删除之后数据文件大小应该减小到原来的 2/3,但执行删除之后数据文件大小并没有缩小

e. 需要通过执行以下命令对表进行优化操作

OPTIMIZE TABLE tablename\G

可以看出优化命令是通过 recreate + analyze 语句来实现的

优化之后数据文件大小缩小到 144M


③ 优化

  在实际的开发中,尽量少去使用大文本字段(blob 或 text),如果实在要使用它,应该采取一些手段来优化查询性能。

a. 不必要时避免检索大型的 BLOB 或TEXT 值

【演示】

改变下 t8 表的结构,添加一个时间戳字段

插入大量数据,然后再插入一笔 id = '4' 的数据,分别执行带和不带检索大文本字段的 SQL,看执行耗时,可以发现,不检索大文本字段的 SQL 快了不少

b. 适当使用索引

从上面的测试结果可以看出,执行一条查询 SQL 耗时达到几百毫秒,这显然不能让我们满意,所以要适当使用索引,来提升检索数据的速度。

【演示】

建立下面的表,并插入大量数据

检索一条不带索引的数据,耗时较长

为查询条件字段 time 建立索引,查询速度大大提升

c. 合成索引

如果查询条件用到的字段是大文本字段本身,那么为整个大文本字段建立索引就不太合适了,可以通过合成索引的方式来提高性能

合成索引,就是根据大文本字段的内容建立一个散列值,并把这个值存储在单独的数据列中,接下来就可以通过检索散列值找到数据行了。但是,这种技术只能用于精确匹配的查询。

【演示】

查询某条记录,通过响应的散列值来查询

d. 前缀索引

合成索引只能用于精确匹配,如果要对大文本字段进行模糊查询,可以使用前缀索引,即只为字段的前 n 列创建索引

e. 把 blob 和 text 列分离到单独的表中

  可以有效减少主表中的碎片,显著减小主表的数据量从而获得性能优势,主表在执行 select * 查询的时候也不会再通过网络传输大量的 blob 或 text 值。


(3)BINARY 和 VARBINARY

  BINARY 和 VARBINARY 的区别类似于 CHAR 和 VARCHAR,只是存储的二进制字符串,CHAR 存储数据时会用空格填充多余空间,取出来使用时再去除空格,而 BINARY 则会用 "0x00" (零字节)填充以达到指定的字段定义长度,所以插入时 "a" 变为 "a\0\0"



4、枚举类型

  ENUM 类型不是SQL标准,属于MySQL,而其他DBMS不一定有原生的支持。MySQL 对其进行存储时,实际上用的是一个整数,比如定义一个性别的字典 gender enum('M', 'F'),存储 'M' 时用的是1,存储 'F' 用的是 2,如下所示:

  所以对 1~255 个成员的枚举需要 1 个字节存储;对于 255~65535 个成员,需要用 2 个字节存储。最多允许有 65535 个成员。(但实际使用中,超过 20 个值的枚举,就会变得难以管理和使用)

  枚举类型忽略大小写,并且插入字符 '1' 跟数字 1 的效果是一样的,字段未声明为 NOT NULL 的情况下允许插入 NULL,但不允许插入空字符串因此不在字段范围内。

PS. 除非枚举的个数比较少,并且保持稳定不变(比如性别),否则尽量不要使用枚举,关于枚举类型的种种弊端,可以参考:MySQL 枚举类型的“八宗罪”。

5、集合类型

  SETENUM 类型非常类似,也是一个字符串对象,可以包含 0~64 个成员。根据成员个数,占用的存储空间大小不同,一个字节可存储 0~8 个成员,所以集合最多需要 8 个字节来存储。

  SETENUM 最大的区别在于 SET 类型一次可以选取多个成员,而 ENUM 则只能选一个。

  SET 类型允许从值集合中选择任意 1 个或多个元素进行组合,对于输入的值只要在允许值的组合范围内,都可以正确地注入到 SET 类型的列中。如果注入的值中有重复则自动去重,比如 'a,d,a' 自动去重为 'a,d';值不在允许范围内会报错比如 'a,f'



6、JSON 类型

  MySQL 从 5.7.8 就开始支持 JSON 类型,在此之前一般使用 VARCHAR 或 TEXT 来保存 JSON 格式数据,使用 JSON 类型相比后者的好处有:

  • MySQL 会检查数据的格式是否为 JSON,不是则插入时会报错。
  • MySQL 提供了一组操作 JSON 数据的内置函数,可以方便地提取各类数据,可以修改特定的键值。
  • 存储在 JSON 列中的 JSON 数据被转换成内部的存储格式,允许快速读取。

(1)使用场景

在什么场景下使用 JSON 类型,或者将 JSON 字符串存储在 VARCHAR、TEXT 类型中?

json作为一种自带结构的文本使得结构信息与可以与数据库解耦

举个栗子,首先在不使用json的时候,如果我们要设计一个User对象。则数据表中的信息如下:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `email` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

那我们设计的这个系统的User的结构信息是这样传递的:

  • 表现层(html/css/anjular/reace/vue……):根据需求展示用户对象各个属性

  • 模型层(java/php/c++/pyhton……):处理用户对象

  • DAO层(Mybatis/Hibernate……):完成对象关系的映射

  • 持久层(MySql/SqlServer……):对各个字段进行存储

因此我们可以看到,在这个系统中,User对象的结构信息是从表现层到持久层是完全贯通且一致的。这样的优点是提高了一致性,使得代码更容易阅读。但有一个重要的问题:耦合太高,扩展不不友好

假设我们要增加给User对象增加一个属性age。那我们各个层都要进行修改,例如数据库层结构需要修改为:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `email` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

同时,其他各层都要进行修改。

而引入json进行存储之后。假设我们将主要信息字段id、name字段进行保留,而次要信息字段转为json。则数据表中的信息变为:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `detail` json(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

对于已经放入detail中的次要信息而言,User的结构信息是这样传递的:

  • 表现层(html/css/anjular/reace/vue……):根据需求展示用户对象各个属性
  • 模型层(java/php/c++/pyhton……):进行json结构与模型的映射
  • DAO层(Mybatis/Hibernate……):无需完成属性与字段的映射
  • 持久层(MySql/SqlServer……):无需了解字段详情

这样,使得User的次要结构信息可以与数据库解耦。当User中再增加一个属性age时,我们只需要在模型层增加一个age属性,甚至连序列化操作都不需要变动就可以直接在表现层进行使用

NoSql数据库中的文档数据库便多用json进行数据存储,来实现与对象结构的解耦。而Mysql中使用json类型,其实是向这种方式的一种靠拢。我们可以:

  • 将经常查询的属性采用独立字段
  • 将不经常查询且变动频繁的字段存入json中

从而实现运行效率可扩展性之间的平衡。


(2)数据类型相关

  JSON 支持的数据类型包括 NUMBER、STRING、BOOLEAN、NULL、ARRAY、OBJECT 共 6 种。其中后三种允许直接插入,前三种必须包含在数组或对象类型中,如图所示:

同时 ARRAY 和 OBJECT 可以嵌套引用。


(3)函数

① JSON_TYPE

  判断插入的 JSON 数据属于哪种类型。

② JSON_VAILD

  判断一个 JSON 数据是否合法。JSON 类型严格区分大小写,NULL 要用 null 表示,布尔类型要用小写的 true/false 表示。

③ JSON_OBJECT

  包装 JSON 对象,简化对引号的转义。

  假如要插入的 JSON 字符串中包含无特殊含义的双引号、单引号,必须在字符串中对其用 \\ 进行转义,这叫隐式插入。

  如果使用 JSON_OBJECT 就可以直接使用 \ 进行转义,这叫显式插入,并且不用中括号,分号,语法更加简洁,格式如下:

json_object('k1',v1,'k2',v2,...,'kn',vn)

你可能感兴趣的:([MySQL 之二] 数据类型)