本文首发于 慕雪的寒舍
mysql的数据类型老多了,单开一篇博客记录一下吧
mysql Ver 15.1 Distrib 10.3.28-MariaDB, for Linux (x86_64) using readline 5.1
数据类型 | 说明 | 字节 |
---|---|---|
BIT(M) | 位类型,M指定位数,默认位1,可1-64 | |
TINYINT [UNSIGNED] | 有符号-127 ~ 128 ;无符号 0 ~ 255 |
1 |
BOOL | 用0和1表示真假(实际为tinyint(1) ) |
1 |
SMALLINT [UNSIGNED] | 有符号-2^15 ~ 2^15 -1 ; 无符号2^16 -1 |
2 |
MEDIUMINT [UNSIGNED] | 有符号-2^23 ~2^23 -1 ; 无符号 2^24 -1 |
3 |
INT [UNSIGNED] | 有符号-2^31 ~ 2^31 -1 ; 无符号2^32 -1 |
4 |
BIGINT [UNSIGNED] | 有符号-2^63 ~ 2^63 -1 ; 无符号2^64 -1 |
8 |
FLOAT [(M,D)] [UNSIGNED] | M指定显示长度,D指定小数位数 | 4 |
DOUBLE [(M,D)] [UNSIGNED] | 同上,精度更高 | 8 |
DECIMAL (M,D) [UNSIGNED] | 同上,M和D必须给出 |
和编译器中会对数据进行截断
不同,mysql中的数据范围必须严格遵守其定义的范围
MariaDB [hello]> create table test(
-> id TINYINT UNSIGNED);
Query OK, 0 rows affected (0.021 sec)
这里我创建了一个使用TINYINT
类型的表,向里面插入如下的正确数据
MariaDB [hello]> insert into test value (3);
Query OK, 1 row affected (0.040 sec)
MariaDB [hello]> insert into test value (0);
Query OK, 1 row affected (0.040 sec)
MariaDB [hello]> insert into test value (10);
Query OK, 1 row affected (0.007 sec)
再尝试插入负数和超过范围的数
MariaDB [hello]> insert into test value (-1);
ERROR 1264 (22003): Out of range value for column 'id' at row 1
MariaDB [hello]> insert into test value (300);
ERROR 1264 (22003): Out of range value for column 'id' at row 1
MariaDB [hello]>
可以看到,mysql阻止了我们的插入。
这就是mysql对应用层的约束,你必须遵守先前对表中对字段类型的定义和范围的要求,才能将数据插入到数据库中。
这样就保证了,我们在mysql中看到的数据,是能保证范围正确的!
并不会出现这个数据是因为截断而被存入的缺省值,从而造成的非精确问题。
当然,你要是在代码中强转数据插入,mysql肯定看不出来。但那样就是程序云的锅了,和mysql本身无关!
注意,当一个无符号数据无法被int类型存放的时候,我们不要用无符号int来存放这个数据,而应该使用bigint。这样才能保证数据一定能被存入。
而我们要存放的数据没有负数的时候(比如年龄、当前时间戳)则建议使用无符号数来存放。因为这样能显式告知其他需要操作这个数据库的人,当前字段是没有负数的。
BIT类型就是用比特位来表示一些0/1的信息
这些数据都是只有真假区分的,使用bit来保存的时候,就能节省一定的空间。
FLOAT [(M,D)] [UNSIGNED]
浮点数这里的M和D是什么意思呢?
alter table test add ft float(4,2) after id;
这里我先往之前已有的test表中插入一个浮点数,来测试一下情况
MariaDB [hello]> desc test;
+-------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------------+------+-----+---------+-------+
| id | tinyint(3) unsigned | YES | | NULL | |
| ft | float(4,2) | YES | | NULL | |
+-------+---------------------+------+-----+---------+-------+
2 rows in set (0.002 sec)
当插入一个整数时,会自动后补0
MariaDB [hello]> insert into test values (1,99);
Query OK, 1 row affected (0.007 sec)
MariaDB [hello]> select * from test;
+------+-------+
| id | ft |
+------+-------+
| 3 | NULL |
| 0 | NULL |
| 10 | NULL |
| 1 | 99.00 |
+------+-------+
4 rows in set (0.000 sec)
插入负数也是没问题的
MariaDB [hello]> insert into test values (1,-99.3);
Query OK, 1 row affected (0.008 sec)
MariaDB [hello]> select * from test;
+------+--------+
| id | ft |
+------+--------+
| 3 | NULL |
| 0 | NULL |
| 10 | NULL |
| 1 | 99.00 |
| 1 | -99.30 |
+------+--------+
5 rows in set (0.001 sec)
但当你想插入如下数字的时候,就开始报错了
MariaDB [hello]> insert into test values (1,-99.99542);
ERROR 1264 (22003): Out of range value for column 'ft' at row 1
MariaDB [hello]> insert into test values (1,100.99542);
ERROR 1264 (22003): Out of range value for column 'ft' at row 1
MariaDB [hello]> insert into test values (1,100.9942);
ERROR 1264 (22003): Out of range value for column 'ft' at row 1
MariaDB [hello]> insert into test values (1,100.99);
ERROR 1264 (22003): Out of range value for column 'ft' at row 1
简单总结
0.994
,不可以插入0.995
(临界数据只能四舍,不支持五入,因为五入了之后就相当于超限了)30.945
会被四舍五入为30.95
float(4,2)
的范围就是 -99.99 ~ 99.99
; 如果是无符号,则为 0 ~ 99.99
MariaDB [hello]> insert into test values (1,30.945);
Query OK, 1 row affected (0.003 sec)
MariaDB [hello]> select * from test;
+------+--------+
| id | ft |
+------+--------+
| 3 | NULL |
| 0 | NULL |
| 10 | NULL |
| 1 | 99.00 |
| 1 | -99.30 |
| 1 | -99.34 |
| 1 | 30.95 |
+------+--------+
7 rows in set (0.000 sec)
DECIMAL (M,D) [UNSIGNED]
这个数据类型和float的区别也是精度上的
在我当前的mariadb中进行测试,同样是插入10.123456789
,float显示的是10.12345695
,decimal显示的是10.12345679
很明显,decimal的精准度更高,正确对10.123456789
进行四舍五入了。而float就把数据给搞坏了。
MariaDB [hello]> drop table test;
Query OK, 0 rows affected (0.046 sec)
MariaDB [hello]> create table test (
-> f1 float(10,8),
-> f2 decimal(10,8));
Query OK, 0 rows affected (0.058 sec)
MariaDB [hello]> insert into test values (10.123456789,10.123456789);
Query OK, 1 row affected, 1 warning (0.010 sec)
MariaDB [hello]> select * from test;
+-------------+-------------+
| f1 | f2 |
+-------------+-------------+
| 10.12345695 | 10.12345679 |
+-------------+-------------+
1 row in set (0.001 sec)
所以,如果需要存储小数,最好使用decimal
这个和mysql无关,只是记录一下
数据类型 | 说明 |
---|---|
CHAR (SIZE) | 定长字符串,最大255 |
VARCHAR (SIZE) | 可变长度字符串,最大长度 65535 |
BLOB | 二进制数据 |
TEXT | 大文本,不支持全文索引,不支持默认值 |
在mysql中,size并不是字节,而是字符数量。
char(2)
它可以存放两个英文字母,两个符号,也可以是两个中文。但实际上两个中文占用的字节数并非2。
比如下方c1和c2分别是char(3) varchar(3)
,可以插入3字的中文
MariaDB [hello]> select * from test;
+---------+-----------+
| c1 | c2 |
+---------+-----------+
| 1ca | 23a |
| 123 | 234 |
| 3 | 23 |
| 你好
| 我在呢 |
+---------+-----------+
4 rows in set (0.001 sec)
超过2个字符的数据会被拒绝插入;
MariaDB [hello]> insert into test values ('1ca','23a3');
ERROR 1406 (22001): Data too long for column 'c2' at row 1
表中提到,这两个除了最大长度的限制,还有一个区别是定长/变长
简单理解为,char是C语言中的char*
类型的字符串,如果定义为CHAR(3)
,就无论如何都会开3个字符的空间来存放数据,不管你的字符串是1个字符还是2个字符;
而varchar是C++的string,它可以动态开辟长度,如果VARCHAR(20)
,当字符串只有3个的时候,只会占用3字节的空间。既然是string,那就需要有额外空间来存放字符串的长度。在mysql中,varchar会采用1-3
字节的空间来存放VARCHAR
数据类型的当前长度。
数据类型 | 说明 |
---|---|
DATE | yyyy-mm-dd |
DATETIME | yyyy-mm-dd hh:mm:ss |
TIMESTAMP | 时间戳 |
这个应该很好看明白,时间类型可以很方便的表示一些时间
在我的视频点播项目中,就是用了如下语句设置了一个TIMESTAMP
字段,做为视频的上传时间
insert_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '视频创建时间',
但我发现,即便是用了TIMESTAMP
,查询的时候依旧显示的是可读时间
需要使用如下的语句进行查询,才会显示时间戳的数字
MariaDB [vod_system]> SELECT UNIX_TIMESTAMP(insert_time) AS timestamp FROM tb_video;
+------------+
| timestamp |
+------------+
| 1683213543 |
| 1683256245 |
+------------+
2 rows in set (0.001 sec)
一般情况下,保存时间戳是更好的选择,因为不管你是在那个时区,时间戳都是统一的。我们可以在应用层将时间戳通过函数(一般都有专门的库函数来转化)成对应时区的可读时间。
但如果你能特别确定你的应用只会在当前时区使用,比如我写了一个数据纯本地化的应用,在哪里部署就使用那个地方的时间。这时候就可以选择使用date
类型来存放str字符串,省去了在应用层重新对时间操作的繁琐。
而且,像python这样的语言,也是支持将特定格式的str时间转回时间戳的。
数据类型 | 说明 |
---|---|
ENUM | ENUM是一个字符串对象,其值来自表创建时在列中规定的枚举变量其中之一的值 |
SET | SET可以有0个或多个值,其值来之表创建时规定允许的一列值。指定包括多个set成员的set列值时,各成员之间需要用逗号风格。set成员值本身不能有逗号 |
枚举类型和我们在C语言中定义的类似,可以作为一种数据的选项
weekday ENUM('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday') NOT NULL
比如我们可以定义一个weekday的枚举类型,来表示星期几
这样就能保证,这一列的数据,就只能是这个枚举类型之中的数据,而不会出现mon
、wed
这样的缩写星期(在代码中不方便判断)
set和enum是类似的,但是可以多选
hobby set('跑步','羽毛球','乒乓球','游泳','跳绳')
当我们插入数据的时候,可以用逗号插入多个set中的数据
insert into test values ('跑步,游泳');
MariaDB [hello]> select * from test;
+---------------+
| hobby |
+---------------+
| 跑步,游泳 |
+---------------+
1 row in set (0.000 sec)
如果尝试插入set中不包含的信息,则会报错
MariaDB [hello]> insert into test values ('跑步,游泳,代码');
ERROR 1265 (01000): Data truncated for column 'hobby' at row 1
MariaDB [hello]>
这个set就可以用于收集表来统计用户固定的爱好的情况,让用户来多选,再插入数据库
因为可以插入多个,这就要求set中每一个元素本身不能包含,
(不能和关键字冲突)
比如如下就是不允许的
hobby set('跑步','羽毛球','乒乓球','游泳','跳,绳')
这张表会被拒绝创建
MariaDB [hello]> create table test (
-> hobby set('跑步','羽毛球','乒乓球','游泳','跳,绳'));
ERROR 1367 (22007): Illegal set '跳,绳' value found during parsing
MariaDB [hello]>
除了用set中的数据,我们还可以用数字来插入这些元素
MariaDB [hello]> insert into test values (1);
Query OK, 1 row affected (0.008 sec)
MariaDB [hello]> select * from test;
+---------------+
| hobby |
+---------------+
| 跑步,游泳 |
| 跑步 |
+---------------+
2 rows in set (0.001 sec)
多尝试几个,会发现结果很奇怪
,并不是当时定义表中,set元素的下标
MariaDB [hello]> insert into test values (2);
Query OK, 1 row affected (0.009 sec)
MariaDB [hello]> insert into test values (3);
Query OK, 1 row affected (0.008 sec)
MariaDB [hello]> insert into test values (4);
Query OK, 1 row affected (0.003 sec)
MariaDB [hello]> select * from test;
+------------------+
| hobby |
+------------------+
| 跑步,游泳 |
| 跑步 |
| 羽毛球 |
| 跑步,羽毛球 |
| 乒乓球 |
+------------------+
5 rows in set (0.001 sec)
比如你打算插入0的时候,会发现是一个空(但不是NULL)
MariaDB [hello]> insert into test values (0);
Query OK, 1 row affected (0.041 sec)
MariaDB [hello]> select * from test;
+------------------+
| hobby |
+------------------+
| 跑步,游泳 |
| 跑步 |
| 羽毛球 |
| 跑步,羽毛球 |
| 乒乓球 |
| |
+------------------+
6 rows in set (0.001 sec)
实际上,这里是用位图
来表示每一个数据的位置的
hobby set('跑步','羽毛球','乒乓球','游泳','跳绳')
这里一共有5个数据,对应就是5个字节
0 0 0 0 0 # 啥都不插入
0 0 0 0 1 # 插入跑步
0 0 0 1 0 # 插入羽毛球
0 0 0 1 1 # 插入跑步,羽毛球
# 以此类推
但当我们尝试插入一个超过5个比特能表示的数据(31)时,会发生什么呢?
MariaDB [hello]> insert into test values (1000);
ERROR 1265 (01000): Data truncated for column 'hobby' at row 1
MariaDB [hello]> insert into test values (32);
ERROR 1265 (01000): Data truncated for column 'hobby' at row 1
不给插入辣!
而插入31是可以的,也就是5个爱好都带上
MariaDB [hello]> insert into test values (31);
Query OK, 1 row affected (0.042 sec)
MariaDB [hello]> select * from test;
+------------------------------------------+
| hobby |
+------------------------------------------+
| 跑步,游泳 |
| 跑步 |
| 羽毛球 |
| 跑步,羽毛球 |
| 乒乓球 |
| |
| 跑步,羽毛球,乒乓球,游泳,跳绳 |
+------------------------------------------+
7 rows in set (0.001 sec)
既然能用数字插入,就能用数字查询
MariaDB [hello]> select * from test where hobby=31;
+------------------------------------------+
| hobby |
+------------------------------------------+
| 跑步,羽毛球,乒乓球,游泳,跳绳 |
+------------------------------------------+
1 row in set (0.001 sec)
select find_in_set('a','a,b,c');
这个函数能帮我们查看一个数据是否在定义的set中
MariaDB [hello]> select find_in_set('a','a,b,c');
+--------------------------+
| find_in_set('a','a,b,c') |
+--------------------------+
| 1 |
+--------------------------+
1 row in set (0.000 sec)
而在查询的时候,就可以用这个来筛选出爱好有其中一项的人
MariaDB [hello]> select * from test where find_in_set('游泳',hobby);
+------------------------------------------+
| hobby |
+------------------------------------------+
| 跑步,游泳 |
| 跑步,羽毛球,乒乓球,游泳,跳绳 |
+------------------------------------------+
2 rows in set (0.001 sec)
这样就成功把爱好中有游泳的人给筛选出来了!
而且我们还可以用or (and)
来连接两个条件,实现更大范围的查询。
MariaDB [hello]> select * from test where find_in_set('游泳',hobby) or find_in_set('羽毛球',hobby);
+------------------------------------------+
| hobby |
+------------------------------------------+
| 跑步,游泳 |
| 羽毛球 |
| 跑步,羽毛球 |
| 跑步,羽毛球,乒乓球,游泳,跳绳 |
+------------------------------------------+
4 rows in set (0.001 sec)
Over!