SQL 的学习比较零散,本文可能会出现部分后续要学习的内容,例如 WHERE 语句等。在初学时只需先记住它的作用,后续学习再回头看就明白了。
在计算机中,每一种数据类型都是被精心设计的,在这个寸土寸金的地方,每一个比特都应该有它的作用。从实际应用的角度看,数据库会存储成千上万甚至上亿条数据,再小单位的数据类型一旦乘以一个很大的基数,也是很大的。
和语言的数据类型类似,数据库的数据类型是用来定义和约束数据的格式、范围和操作的。不同的数据类型有不同的特点和用途,可以满足不同的数据需求和场景。数据库有那么多数据类型,主要是为了:
分类 | 数据类型 | 说明 |
---|---|---|
数值类型 | BIT(M) | 位类型:M 指定位数,默认值为 1,范围为 1-64 |
BOOL | 布尔类型:使用 1 表示真,使用 0 表示假 | |
TINYINT [UNSIGNED] | 占用 1 字节,默认为有符号 | |
SMALLINT [UNSIGNED] | 占用 2 字节,默认为有符号 | |
MEDIUMINT [UNSIGNED] | 占用 3 字节,默认为有符号 | |
INT [UNSIGNED] | 占用 4 字节,默认为有符号 | |
BIGINT [UNSIGNED] | 占用 8 字节,默认为有符号 | |
FLOAT[(M,D)] [UNSIGNED] | M 指定显示长度,D 指定小数位数,占用 4 字节 | |
DOUBLE[(M,D)] [UNSIGNED] | M 指定显示长度,D 指定小数位数,占用 8 字节 | |
DECIMAL(M,D) [UNSIGNED] | M 指定显示长度,D 指定小数位数,每 4 个字节表示 9 个数字,小数点占用 1 字节 | |
文本、二进制类型 | CHAR(L) | 固定长度字符串:L 指定字符串长度,最大为 255 |
VARCHAR(L) | 可变长度字符串:L 指定字符串长度上限,最多占用 65535 字节 | |
BLOB | 用于存储二进制数据 | |
TEXT | 用于存储大文本数据 | |
时间日期 | DATE / DATETIME | 日期类型:YYYY-MM-DD 格式 / YYYY-MM-DD HH:MM:SS 格式 |
TIMESTAMP | 时间戳:以 YYYY-MM-DD HH:MM:SS 格式进行显示 | |
字符串类型 | ENUM | 枚举类型:ENUM 类型的取值范围需要在定义字段时进行指定,设置字段值时只允许从成员中选取单个值,其所需的存储空间由定义 ENUM 类型时指定的成员个数决定 |
SET | 集合类型:SET 类型的取值范围需要在定义字段时进行指定,设置字段值时可以从成员中选取一个或多个值,其所需的存储空间由定义 SET 类型时指定的成员个数决定 |
MySQL 不像 C/C++等编程语言一样 (虽然它是 C++实现的),允许用户为错误的数据类型赋值,让用户自己承担后果。作为工业级数据管理系统,这种情况是不被允许出现的,所以 MySQL 会严格检查数据和属性的类型是否匹配。
数据库虽然在数据流上是比较靠后的层次,但是数据类型是和上层业务强相关的,所以在定义列属性时,需要根据实际情况。
这就体现了 MySQL 的“约束性”,它就是数据库保证数据安全性的第一环,约束的是程序员的行为。
在 t1 表中测试:
当用户插入 128 或 -129 时,MySQL 检查到数值不在 1 字节的范围,报错。无符号也是一样的。
INT(N) 中的 N 表示显示宽度,它只用于显示,并不能限制取值范围和占用空间。显示宽度是指在输出结果中显示整数值时所用的最小字符数。如果整数值的位数小于显示宽度,MySQL 会在左边用空格或零来填充。
对于 INT 而言:
例如创建一个表,不指定 INT 的显示长度,然后再用show
命令查看:
用法:bit(m),其中 m 是位值的长度,范围是 1 到 64 。如果省略 m ,默认值是 1 。例如,以下两种声明是等效的:
column_name bit(1);
column_name bit;
bit 类型的字面值可以用 b’val’ 或 0bval 表示,其中 val 是只包含 0 和 1 的二进制值。开头的 b 或 B 可以省略,但前导的 0b 是区分大小写的,不能用 0B 。例如,以下都是有效的字面值:
b'101'
0b101
101
bit 类型在存储和显示时会有一些特殊的处理。如果插入一个长度小于 m 的位值,MySQL 会在左边用 0 填充。如果显示一个位值,MySQL 会去掉前导的 0 。如果想要显示完整的位值,可以用 bin 函数或 lpad 函数。例如:
create table test (
b bit(4)
);
insert into test values (b'11');
select b from test; -- 输出 11
select bin(b) from test; -- 输出 11
select lpad(bin(b),4,'0') from test; -- 输出 0011
下面用这个表来测试:
mysql> create table t2(
-> id int,
-> x bit(8)
-> );
如果像第二条这样直接插入一个整数,而不是一个用b''
包含的二进制序列,那么 MySQL 会将这个十进制整数转为二进制,也就是 10 转成二进制 1010。
在显示 bit 类型时,MySQL 会将它的二进制序列转化为十进制对应的 ASCII 值,然后再显示。在这个例子中,第一行的 ASCII 值是 2,它对应的是 STX 控制字符(Start of Text),表示正文或数据的开始,不显示任何内容(通常和 EXT,End of Text,搭配使用);第二行的 ASCII 值是 10,对应的是 LF 控制字符(Line Feed),它的作用是换行。
ASCII 码对照表
如果插入值为 65,97 的整数:
bit 类型可以用来存储状态值,比如真、假或是、否等。例如,我们可以用 bit 类型来表示一个人是否在工作:
mysql> create table t3(
-> id int,
-> working bit(1)
-> );
Query OK, 0 rows affected (0.02 sec)
一个 bit 的范围只有 0 和 1,超出这个范围的值,不被 MySQL 允许插入:
那么 bit 的范围就取决于 m 的大小,即 m 位二进制序列对应的十进制的范围。
bit 类型可以用来存储状态值,比如真、假或是、否等。bit 类型可以节省存储空间,提高查询效率,但也有一些注意事项:
在插入和更新数据时,需要用 b’val’ 或 0bval 的格式来表示二进制的位值,其中 val 是只包含 0 和 1 的字符串。如果直接插入一个整数,MySQL 会把它当成十进制的数值,然后转换成二进制的位值。
在显示和查询数据时,MySQL 会把 bit 类型的值当成一个整数来显示,而不是一个位值。如果想看到位值的形式,需要用 bin 函数或 lpad 函数来格式化输出。
在进行条件判断或逻辑运算时,需要注意 bit 类型的值和其他类型的值之间的转换规则。比如,bit 类型的值和字符串类型的值比较时,会把字符串类型的值转换成整数类型的值。
FLOAT[(M,D)]:M 指定显示数值的总长度,D 指定小数位数,占用 4 字节。M 的范围是 1~24,默认是 10。D 的范围是 0~M,默认是 0。
在表 t4 中测试:
mysql> create table t4(
-> id int,
-> num float(4, 2)
-> );
其中 D 必须小于等于 M,否则会出现这样的提示:
ERROR 1427 (42000): For float(M,D), double(M,D) or decimal(M,D), M must be >= D (column 'num').
值得注意的是,MySQL 检查的是这个数值的绝对值在四舍五入(即向零取整)后的结果:
向零取整:想象一下有负数和正数的数轴,对数值的绝对值做四舍五入,就是向中间的 0 取整。这里的整数是对于规定的 M 而言的,例如 M 是 2,也就是规定小数保留 2 位,那么取整时就保留 2 位。
UNSIGNED FLOAT 也遵守同样的规则,只是不能插入负数。
DECIMAL 是精度更高的 FLOAT。
mysql> create table t5(
-> num1 float(8, 6),
-> num2 decimal(8, 6)
-> );
浮点数存储有精度损失,根本原因是二进制无法精确表示浮点数。
[注]
DECIMAL 和 FLOAT 的底层实现是不同的。DECIMAL 类型是用十进制来存储每个数字,并且每 9 个数字占用 4 个字节。比如,一个 DECIMAL(18,9) 类型的值,会被分成两部分:整数部分和小数部分,每部分占用 4 个字节,共占用 8 个字节 。FLOAT 类型是用二进制来存储浮点数,并且每个浮点数占用 4 个字节。一个 FLOAT 类型的值,会被分成四部分:符号位、指数位、基数位和尾数位,每部分占用一定的二进制位 。
用法:
CHAR(N)
其中 N 表示的是字符而不是字节,不论是英文字母还是中文字符,都视为 1 个字符。
mysql> create table t6(
-> str char(4)
-> );
注意,SQL 的规定是用一对单引号表示字符串,但是用双引号也是被语法允许的。如果单引号和双引号是字符串的一部分,使用\
转义。
用法和 CHAR(N) 一样,也是 VARCHAR(N)。
mysql> create table t7(
-> str varchar(4)
-> );
VARCHAR 和 CHAR 在插入记录时不是都遵守同样的规则吗?VARCHAR 的“变长”体现在哪里?
VARCHAR 和 CHAR 都是用来存储字符串的数据类型,但是它们有一些不同之处:
VARCHAR 类型的“变长性”体现在它可以根据实际值的长度来分配存储空间,而不是固定地占用指定的字节数。这样可以避免浪费空间,也可以适应不同长度的字符串。
VARCHAR 类型的限制和规则是:
下面是一些例子来说明 VARCHAR 类型的特点和限制:
create table t4 (c VARCHAR(20)) charset = gbk;
,那么 c 列可以存放 20 个字符,无论是数字、字母还是汉字(每个汉字占 2 个字节),最大占用 40 个字节(再加上一个字节记录长度)。create table t5 (c VARCHAR(20)) charset = utf8;
,那么 c 列可以存放 20 个字符,无论是数字、字母还是汉字(每个汉字占 3 个字节),最大占用 60 个字节(再加上一个或两个字节记录长度)。create table t6 (c1 CHAR(10), c2 VARCHAR(10)) charset = gbk;
,那么 c1 列总是占用 20 个字节(再加上一个字节记录长度),不管实际值有多长,而 c2 列只占用实际值的字节数再加上一个字节记录长度。如果插入一条数据insert into t6 values ('abc', 'abc');
,那么 c1 列占用 21 个字节,存储为'abc'
+ 17 个空格,而 c2 列占用 4 个字节,存储为 3 + ‘abc’(3 表示长度)。create table t7 (c VARCHAR(10)) charset = gbk;
,那么 c 列可以存放 10 个字符,最大占用 20 个字节(再加上一个字节记录长度)。如果插入一条数据insert into t7 values ('abcdefghijk');
,那么 c 列会被裁剪为 10 个字符,存储为 10 + 'abcdefghij'
,并且会产生一条警告。如何选择二者?
BLOB 用来存储二进制大对象,比如图片、视频、音频等。BLOB 有四种不同的子类型,它们只是在存储的最大容量上有所区别,分别是:
TEXT 用来存储长文本数据,比如文章、评论、博客等。TEXT 类型有四种不同的类型,它们只是在存储的最大容量上有所区别,分别是:
TEXT 类型的数据不会被自动截断,也不会删除或填充空格。TEXT 类型的数据不存储在数据库服务器的内存中,因此每次查询时都需要从磁盘读取,这会比 CHAR 和 VARCHAR 类型慢得多。
在使用上,可以当做一个普通的字符串类型来使用,如文章或博客这种需要持久化的数据,一般用 TEXT 保存。
持久化,也就是将内存中的数据写入磁盘中,以便后续再次使用。
NULL
和''
它们是 MySQL 中两种不同的空值表示方式:
NULL
表示一个未知的或未定义的值,而 ''
表示一个空字符串。NULL
在参与比较或计算时,结果仍然是 NULL
,而 ''
可以正常进行比较或计算。NULL
在进行统计或求和时,会被忽略,而 ''
会被计算在内。NULL
需要用 IS NULL
或 IS NOT NULL
来判断,而 ''
可以用 = 或 <> 来判断。NULL
需要占用额外的空间来记录其状态,而 ''
不占用空间。实际上,select 这个 MySQL 命令可以求表达式的值,例如:
由此可见,NULL
表示“什么都没有”,也就是“无”;而''
表示一个空字符串。注意,在 MySQL 中,''
会被转化为转化为一个浮点数0.0
,所以对它做乘法的结果是 0,假如你用另一个浮点数和它做运算,也是会出现浮点数精度误差的。
类型 | 大小 ( bytes) | 范围 | 格式 | 用途 |
---|---|---|---|---|
*DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 |
TIME | 3 | ‘-838:59:59’/‘838:59:59’ | HH:MM:SS | 时间值或持续时间 |
YEAR | 1 | 1901/2155 | YYYY | 年份值 |
*DATETIME | 8 | ‘1000-01-01 00:00:00’ 到 ‘9999-12-31 23:59:59’ | YYYY-MM-DD hh:mm:ss | 混合日期和时间值 |
*TIMESTAMP | 4 | ‘1970-01-01 00:00:01’ UTC 到 ‘2038-01-19 03:14:07’ UTC 结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038 年 1 月 19 日 凌晨 03:14:07 | YYYY-MM-DD hh:mm:ss | 混合日期和时间值,时间戳 |
[注] 标*
表示常用项。
mysql> create table t8(
-> time1 date,
-> time2 datetime,
-> time3 timestamp
-> );
查看表结构:
其中,timestamp 列属性不允许为 NULL,并且默认值为 CURRENT_TIMESTAMP,它的含义是:如果你在创建一个时间字段时,使用了 DEFAULT CURRENT_TIMESTAMP 或者 ON UPDATE CURRENT_TIMESTAMP,那么数据库会自动维护这个字段的值,不需要你手动指定。
下面是一个例子,着重理解第三个时间戳列属性:
在插入时没有指定时间戳,那么 MySQL 会自动插入当前的时间戳。
我们可以利用这个特性,让 MySQL 维护这个列属性。例如在用户发表博客,评论等事件时:
mysql> create table t9(
id int,
nickname varchar(20),
comment text(100),
cmt_time timestamp
);
当用户对评论进行修改,实际上就是 MySQL 对这条记录修改:
MySQL 的 ENUM(枚举) 和 SET(集合) 是两种复合数据类型,它们都可以用来存储一组预定义的字符串值。它们的区别是:
下面是一些例子来说明它们的用法:
mysql> create table t10(
-> name varchar(20),
-> gender enum('男', '女'),
-> hobby set('音乐', '电影', '游泳', '足球')
-> );
请注意在插入 SET 的多个参数时,只需要用英文逗号隔开,被包含在一对单引号中。
MySQL 为了存储的效率,它将 SET 中的一组预定义的字符串视为一组二进制位。当用户查询或插入 SET 值时,可以使用字符串(上面的做法)或者(十进制)数字来表示,但是 MySQL 实际上是用二进制位来存储和比较的。
在这个例子中,插入 SET 记录使用的是十进制的整数,整数的合法性取决于 SET 的长度,例如在 SET[‘音乐’, ‘电影’, ‘游泳’, ‘足球’] 中,这 4 个字符串对应的二进制权值位分别是 [1, 2, 4, 8](SET 最多能够存储 64 个字符串),总共 15,即最大值是 15。所以当插入 123 时,是不合法的。
其中 12 的二进制序列是 [0011],对应着 SET 的后两个字符串。
如果你想用二进制序列b'1111'
或者0b1111
插入记录,是不被 SQL 允许的。
虽然语法上允许使用十进制数字插入记录,但是对于后期维护和插入时的人员而言都不友好,一是要进行进制转换,而是可读性差。
另外,SET 和 ENUM 的下标都是从 1 开始的,而不是从 0,这是出于 MySQL 用户不一定是程序员的考虑。
find_in_set(str, str_set)
,这个函数是用来验证 str 这个字符串是否在 str_set 这个集合中的,如果找到则返回下标;找不到则返回 0。
例如在上面这个表中:
MySQL 数据类型 | 菜鸟教程
MySQL 数据类型 | CSDN