MySQL 数据类型详解:从数值到字符串,一篇讲透

1. 前言

写 MySQL 的时候,建表是最基础也最容易翻车的一步。表建得好不好,很大程度上取决于你对数据类型的理解。类型选错了,轻则浪费空间,重则数据存不进去、查询出问题。

这篇博客把 MySQL 里常用的数据类型过一遍,包括数值、字符串、日期、枚举和集合,每个都配上实际执行的 SQL 和结果,方便你照着敲一遍。

2. 数值类型

数值类型是建表时用得最多的,先看分类,再逐个说细节。

2.1 tinyint 类型

tinyint 默认是有符号的,范围是 -128 到 127。如果你往里面塞一个超出范围的值,MySQL 会直接报错。

sql 复制代码
mysql> create table tt1(num tinyint);
Query OK, 0 rows affected (0.02 sec)

mysql> insert into tt1 values(1);
Query OK, 1 row affected (0.00 sec)

mysql> insert into tt1 values(128); -- 越界插入,报错
ERROR 1264 (22003): Out of range value for column 'num' at row 1

mysql> select * from tt1;
+------+
| num  |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

MySQL 的整型默认是有符号的,如果你想让某个字段只能存非负数,可以加上 unsigned 关键字。

sql 复制代码
mysql> create table tt2(num tinyint unsigned);

mysql> insert into tt2 values(-1); -- 无符号,范围是 0 - 255
ERROR 1264 (22003): Out of range value for column 'num' at row 1

mysql> insert into tt2 values(255);
Query OK, 1 row affected (0.02 sec)

mysql> select * from tt2;
+------+
| num  |
+------+
|  255 |
+------+
1 row in set (0.00 sec)

这里有个建议:尽量别用 unsigned。比如 int 类型存不下的数据,int unsigned 同样可能存不下,与其这样,不如在建表时直接把 int 提升成 bigint,省得后面出问题。

2.2 bit 类型

bit 是位字段类型,语法是 bit[(M)],M 表示每个值的位数,范围 1 到 64,不写的话默认是 1。

sql 复制代码
mysql> create table tt4 ( id int, a bit(8));
Query OK, 0 rows affected (0.01 sec)

mysql> insert into tt4 values(10, 10);
Query OK, 1 row affected (0.01 sec)

mysql> select * from tt4; -- 发现很怪异的现象,a的数据10没有出现
+------+------+
| id   | a    |
+------+------+
|   10 |      |
+------+------+
1 row in set (0.00 sec)

这里有个坑:bit 字段在显示的时候,是按照 ASCII 码对应的值显示的。所以存 10 显示不出来,存 65 会显示成 A。

sql 复制代码
mysql> insert into tt4 values(65, 65);
mysql> select * from tt4;
+------+------+
| id   | a    |
+------+------+
|   10 |      |
|   65 | A    |
+------+------+

如果你只需要存 0 或 1,可以定义成 bit(1),这样能省空间。

sql 复制代码
mysql> create table tt5(gender bit(1));

mysql> insert into tt5 values(0);
Query OK, 1 row affected (0.00 sec)

mysql> insert into tt5 values(1);
Query OK, 1 row affected (0.00 sec)

mysql> insert into tt5 values(2); -- 当插入2时,已经越界了
ERROR 1406 (22001): Data too long for column 'gender' at row 1

2.3 小数类型

小数类型主要有 float 和 decimal 两种,各有各的适用场景。

2.3.1 float

float 的语法是 float[(m, d)] [unsigned],M 指定显示长度,d 指定小数位数,占用 4 个字节。

sql 复制代码
mysql> create table tt6(id int, salary float(4,2));
Query OK, 0 rows affected (0.01 sec)

mysql> insert into tt6 values(100, -99.99);
Query OK, 1 row affected (0.00 sec)

mysql> insert into tt6 values(101, -99.991); -- 多的这一点被拿掉了
Query OK, 1 row affected (0.00 sec)

mysql> select * from tt6;
+------+--------+
| id   | salary |
+------+--------+
|  100 | -99.99 |
|  101 | -99.99 |
+------+--------+
2 rows in set (0.00 sec)

float(4,2) 表示的范围是 -99.99 到 99.99,MySQL 在保存值的时候会做四舍五入。如果指定了 unsigned,范围就变成 0 到 99.99。

sql 复制代码
mysql> create table tt7(id int, salary float(4,2) unsigned);

mysql> insert into tt7 values(100, -0.1);
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> show warnings;
+---------+------+-------------------------------------------------+
| Level   | Code | Message                                         |
+---------+------+-------------------------------------------------+
| Warning | 1264 | Out of range value for column 'salary' at row 1 |
+---------+------+-------------------------------------------------+
1 row in set (0.00 sec)

mysql> insert into tt7 values(100, -0);
Query OK, 1 row affected (0.00 sec)

mysql> insert into tt7 values(100, 99.99);
Query OK, 1 row affected (0.00 sec)
2.3.2 decimal

decimal 是定点数,语法是 decimal(m, d),m 指定长度,d 表示小数位数。decimal(5,2) 表示范围是 -999.99 到 999.99,unsigned 的话是 0 到 999.99。

float 和 decimal 最大的区别是精度。float 的精度大约是 7 位,decimal 的整数最大位数 m 是 65,小数最大位数 d 是 30。如果希望精度高,推荐用 decimal。

sql 复制代码
mysql> create table tt8 ( id int, salary float(10,8), salary2 decimal(10,8));

mysql> insert into tt8 values(100,23.12345612, 23.12345612);
Query OK, 1 row affected (0.00 sec)

mysql> select * from tt8;
+------+-------------+-------------+
| id   | salary      | salary2     |
+------+-------------+-------------+
|  100 | 23.12345695 | 23.12345612 | -- 发现decimal的精度更准确
+------+-------------+-------------+

同样的值,float 存出来变成了 23.12345695,decimal 还是 23.12345612。所以对精度有要求的场景,比如金额,直接用 decimal。

3. 字符串类型

字符串类型主要就是 char 和 varchar,这俩是面试常客,也是建表时最容易纠结的。

3.1 char

char 是固定长度字符串,语法是 char(L),L 是存储的长度,单位是字符,最大 255。

sql 复制代码
mysql> create table tt9(id int, name char(2));
Query OK, 0 rows affected (0.00 sec)

mysql> insert into tt9 values(100, 'ab');
Query OK, 1 row affected (0.00 sec)

mysql> insert into tt9 values(101, '中国');
Query OK, 1 row affected (0.00 sec)

mysql> select * from tt9;
+------+--------+
| id   | name   |
+------+--------+
|  100 | ab     |
|  101 | 中国   |
+------+--------+

char(2) 可以放两个字符,字母或汉字都行,但超过 255 就会报错。

sql 复制代码
mysql> create table tt10(id int ,name char(256));
ERROR 1074 (42000): Column length too big for column 'name' (max = 255); use BLOB or TEXT instead

3.2 varchar

varchar 是可变长度字符串,语法是 varchar(L),L 表示字符长度,最大 65535 个字节。

sql 复制代码
mysql> create table tt10(id int ,name varchar(6)); -- 表示这里可以存放6个字符

mysql> insert into tt10 values(100, 'hello');
mysql> insert into tt10 values(100, '我爱你,中国');

mysql> select * from tt10;
+------+--------------------+
| id   | name               |
+------+--------------------+
|  100 | hello              |
|  100 | 我爱你,中国        |
+------+--------------------+

关于 varchar(len) 到底能设多大,这个和表的编码密切相关。varchar 长度可以指定 0 到 65535 之间的值,但有 1 到 3 个字节用于记录数据大小,所以有效字节数是 65532。

  • 表编码是 utf8 时,一个字符占 3 个字节,varchar(n) 的 n 最大值是 65532/3 = 21844
  • 表编码是 gbk 时,一个字符占 2 个字节,varchar(n) 的 n 最大值是 65532/2 = 32766

验证一下:

sql 复制代码
mysql> create table tt11(name varchar(21845))charset=utf8; -- 验证了utf8确实是不能超过21844
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs

mysql> create table tt11(name varchar(21844)) charset=utf8;
Query OK, 0 rows affected (0.01 sec)

3.3 char 和 varchar 怎么选

这个选择其实不难,记住几条原则就行:

  • 如果数据长度确定都一样,用定长 char,比如身份证、手机号、md5
  • 如果数据长度有变化,用变长 varchar,比如名字、地址,但要保证最长的能存进去
  • 定长的磁盘空间比较浪费,但效率高
  • 变长的磁盘空间比较节省,但效率低

定长的意义是直接开辟好对应的空间,变长的意义是在不超过自定义范围的情况下,用多少开辟多少。

4. 日期和时间类型

常用的日期类型有三个:

  • date:日期,格式 yyyy-mm-dd,占用 3 字节
  • datetime:时间日期,格式 yyyy-mm-dd HH:ii:ss,范围从 1000 到 9999,占用 8 字节
  • timestamp:时间戳,从 1970 年开始,格式和 datetime 完全一致,占用 4 字节
sql 复制代码
-- 创建表
mysql> create table birthday (t1 date, t2 datetime, t3 timestamp);
Query OK, 0 rows affected (0.01 sec)

-- 插入数据
mysql> insert into birthday(t1,t2) values('1997-7-1','2008-8-8 12:1:1');
Query OK, 1 row affected (0.00 sec)

mysql> select * from birthday;
+------------+---------------------+---------------------+
| t1         | t2                  | t3                  |
+------------+---------------------+---------------------+
| 1997-07-01 | 2008-08-08 12:01:01 | 2017-11-12 18:28:55 | -- 添加数据时,时间戳自动补上当前时间
+------------+---------------------+---------------------+

-- 更新数据
mysql> update birthday set t1='2000-1-1';
Query OK, 1 row affected (0.00 sec)

mysql> select * from birthday;
+------------+---------------------+---------------------+
| t1         | t2                  | t3                  |
+------------+---------------------+---------------------+
| 2000-01-01 | 2008-08-08 12:01:01 | 2017-11-12 18:32:09 | -- 更新数据,时间戳会更新成当前时间
+------------+---------------------+---------------------+

注意 timestamp 的特性:插入数据时自动补上当前时间,更新数据时也会自动更新成当前时间。这个特性在很多场景下很实用。

5. enum 和 set

这俩是 MySQL 里比较特殊的类型,一个是单选,一个是多选。

5.1 enum 枚举

enum 是"单选"类型,语法是 enum('选项1','选项2','选项3',...)。它只提供若干个选项,最终一个单元格只存其中一个值。出于效率考虑,这些值实际存储的是数字,每个选项依次对应 1,2,3...,最多 65535 个。添加枚举值时也可以直接加数字编号。

5.2 set 集合

set 是"多选"类型,语法是 set('选项值1','选项值2','选项值3',...)。一个单元格可以存其中任意多个值。同样出于效率考虑,这些值实际存储的是数字,每个选项依次对应 1,2,4,8,16,32...,最多 64 个。

这里有个建议:添加枚举值、集合值的时候别用数字方式,不利于阅读。

5.3 实际案例

建一个调查表 votes,调查人的喜好,比如登山、游泳、篮球、武术(可多选),性别(单选)。

sql 复制代码
mysql> create table votes(
    -> username varchar(30),
    -> hobby set('登山','游泳','篮球','武术'), -- 使用数字标识每个爱好,想想Linux权限,采用比特位位置
    -> gender enum('男','女'));  -- 使用数字标识的时候,就是正常的数组下标
Query OK, 0 rows affected (0.02 sec)

insert into votes values('雷锋', '登山,武术', '男');
insert into votes values('Juse','登山,武术',2);

select * from votes where gender=2;
+----------+---------------+--------+
| username | hobby         | gender |
+----------+---------------+--------+
| Juse     | 登山,武术      | 女     |
+----------+---------------+--------+

现在想查找所有喜欢登山的人,如果直接用等值查询:

sql 复制代码
mysql> select * from votes where hobby='登山';
+----------+--------+--------+
| username | hobby  | gender |
+----------+--------+--------+
| LiLei    | 登山   | 男     |
+----------+--------+--------+

这样查不出来所有爱好为登山的人,因为有的人爱好是"登山,武术"。这时候要用 find_in_set 函数。

find_in_set(sub, str_list):如果 sub 在 str_list 中,返回下标;如果不在,返回 0。str_list 是用逗号分隔的字符串。

sql 复制代码
mysql> select find_in_set('a', 'a,b,c');
+---------------------------+
| find_in_set('a', 'a,b,c') |
+---------------------------+
|                         1 |
+---------------------------+

mysql> select find_in_set('d', 'a,b,c');
+---------------------------+
| find_in_set('d', 'a,b,c') |
+---------------------------+
|                         0 |
+---------------------------+

mysql> select * from votes where find_in_set('登山', hobby);
+----------+---------------+--------+
| username | hobby         | gender |
+----------+---------------+--------+
| 雷锋     | 登山,武术     | 男     |
| Juse     | 登山,武术     | 女     |
| LiLei    | 登山          | 男     |
+----------+---------------+--------+

这样就能把所有喜欢登山的人都查出来了。

6. 小结

数据类型这块,记住几个关键点就够了:

  • 整型默认有符号,unsigned 能不用就不用,存不下就升级 bigint
  • bit 类型显示按 ASCII 码,只存 0/1 用 bit(1)
  • 小数精度要求高用 decimal,别用 float
  • 定长用 char,变长用 varchar,注意编码对 varchar 长度的影响
  • timestamp 会自动更新,适合记录操作时间
  • enum 单选、set 多选,查询 set 用 find_in_set

建表前先想清楚每个字段的类型,后面能省不少事。

相关推荐
传奇开心果编程1 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
做运维的阿瑞2 小时前
SQL 面试高频:用户、商品、订单三表查询全解析
数据库·sql·mysql
CJi0NG3 小时前
【自用】MySQL - 语法:DDL、DML、DQL、DCL
数据库·mysql
一技安身4 小时前
【mysql】Windows MySQL 8.4.10 绿色免安装喂饭教程
windows·mysql·adb
斯内普吖4 小时前
(开源)宠物饲养系统实战指南 基于 Java + SSM + Vue + MySQL
java·vue.js·mysql·开源·宠物
传奇开心果编程4 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
ao-weilai4 小时前
MySQL数据库:表约束
数据库·mysql·adb
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程6 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer