
🔥keyipatience:个人主页
🎬作者简介:C/C++后端开发学习者
🌟专栏传送门:《c++》《linux》《c++高阶数据结构》《c++数据结构与算法》《前端学习》
⭐️patience is key in life
一、数据类型分类
| 分类 | 常见类型 | 常见用途 |
|---|---|---|
| 整数 | tinyint、`smallint`、`mediumint`、`int`、`bigint` |
状态、数量、编号 |
| 小数 | float、`double`、`decimal` |
测量值、金额 |
| 位类型 | bit |
位标记、二进制位值 |
| 字符串 | char、`varchar`、`text` |
编码、姓名、文本 |
| 日期和时间 | date、`datetime`、`timestamp` |
日期、业务时间、时间戳 |
| 枚举和集合 | enum、`set` |
固定选项、多个选项 |
二、数值类型
1. 整数类型
| 类型 | 存储空间 | 有符号范围 | unsigned 范围 |
|---|---|---|---|
tinyint |
1 字节 | -128 到 127 | 0 到 255 |
smallint |
2 字节 | -32768 到 32767 | 0 到 65535 |
mediumint |
3 字节 | -8388608 到 8388607 | 0 到 16777215 |
int |
4 字节 | -2147483648 到 2147483647 | 0 到 4294967295 |
bigint |
8 字节 | -9223372036854775808 到 9223372036854775807 | 0 到 18446744073709551615 |
日常开发最常用:
int、bigint主键 id 现在很多项目推荐bigint,防止 int 上限不够
语法 demo
drop table if exists test_num;
create table test_num (
id int primary key auto_increment,
age tinyint, -- 有符号,可以存负数
score tinyint unsigned, -- 无符号,不能负数,0~255
num bigint unsigned
);
insert into test_num(age,score,num) values (18,99,100000);
-- insert into test_num(age,score) values (-5,-10); -- score 无符号,-10 报错
细节:
1:以前 int(11)、tinyint(3) 这种括号里的数字:显示宽度,和存储范围完全无关 只是客户端查询展示的时候,不够位数补空格(配合 zerofill 才生效),mysql8.0 已经不推荐写。 int(11) 照样可以存 2147483647,不是最多只能存 11 位数字
-- zerofill 示例,了解即可,项目几乎不用
create table t_int_demo(
num int(5) zerofill
);
insert into t_int_demo values(12);
-- 查询出来展示:00012
2:unsigned 只是去掉负数,最大值翻倍
比如 tinyint unsigned:0~255,适合年龄、分数、数量这种不会负的数据。
3:auto_increment 配合 unsigned
id int unsigned primary key auto_increment
无符号自增 id,可用 id 数量更多。
选型建议
- 年龄、状态标记:
tinyint - 普通 id:
int - 大数据量、订单 id、分布式主键:
bigint
2. 小数类型
| 类型 | 字节 | 特点 | 使用场景 |
|---|---|---|---|
| float | 4 | 单精度浮点,精度低,有误差 | 温度、权重、不需要精确的科学数据 |
| double | 8 | 双精度浮点,精度比 float 高,依然有误差 | 统计、科学计算,不涉及金额 |
| decimal(m,d) | 变长 | 定点数,精确无误差 | 金额、价格、财务(最重要!) |
decimal (m,d)
-
m:总位数(整数 + 小数加起来)最大65 -
d:小数部分位数 最大30 -
例:
decimal(10,2)最多一共 10 位,小数占 2 位 → 整数最多 8 位 范围:-99999999.99 ~ 99999999.99drop table if exists test_float;
create table test_float(
id int primary key auto_increment,
f float,
d double,
price decimal(10,2)
);insert into test_float(f,d,price) values (0.1,0.1,0.1);
select * from test_float;-- 重点:浮点相加,会出现精度bug
select f + 0.2, d + 0.2, price + 0.2 from test_float;0.1 + 0.2
float、double 可能算出0.30000000000000004这种,不准- decimal 算出来稳稳
0.30
商品价格、订单金额等字段通常使用 decimal。定义精度时要留足整数位和小数位,避免金额增长后超过字段范围。
mysql 8.0.17 起,float(m, d)`和
double(m, d)这种指定总位数和小数位数的写法已弃用。新表通常直接声明float或double;需要精确小数位时使用decimal(p, s)。
3. bit
bit(m) 用于保存位值,m`表示位数,范围为 1 到 64。例如 bit(8) 可以表示 8 位的二进制值。
小 demo
create table demo_bit (
id int primary key auto_increment,
flag bit(1),
bits3 bit(3)
) default character set utf8mb4;
插入写法,两种方式:
-- 方式1:b'二进制串'
insert into demo_bit(flag,bits3) values (b'1', b'101');
-- 方式2:直接写数字(会自动转二进制)
insert into demo_bit(flag,bits3) values (0, 5);
查询的时候注意: 直接 select 看到的是二进制原始字节,不会显示 0/1 ,要用 bin() 函数转成二进制字符串看:
select id, bin(flag), bin(bits3) from demo_bit;
输出:

三、字符串类型
1.char (m) 固定长度
-
m:最大字符数,范围
1 ~ 255 -
存储规则:不管你数据写多长,固定占用 m 个字符对应的字节空间 数据不够 m 个字符 → 末尾自动补空格填充; 查询读取的时候,mysql默认把末尾的空格删掉。
-- demo
create table demo_char (
phone char(11) comment '手机号固定11位'
) default character set utf8mb4;
insert into demo_char(phone) values ('13800138000');
示例:char(11),存'13800138000',utf8mb4 下固定占 11*4=44 字节,就算只存 5 个字符,依然占 44 字节。
优点:长度固定,mysql 定位数据很快,索引性能更好
缺点:短数据浪费存储空间
适合:长度固定不变的数据:手机号、身份证号、验证码、编码
大坑:末尾空格会被吃掉
-- char(4) 存入 'ab '(ab后面两个空格)
-- select 拿出来得到 'ab'
-- where 'ab' = 'ab ' 判定相等!空格不参与比对
2. varchar (m) 可变长度(业务最常用)
- m:允许的最大字符数, 65535 字节(行限制)即最大字符数
65535 / 4 ≈ 16383。 - 存储规则:占用字节 = 真实数据字节 + 长度标记
-
数据字节 ≤255:长度标记占 1 字节
-
数据字节 >255:长度标记占 2 字节
-- demo
create table demo_varchar (
name varchar(20) comment '姓名,长短不一定'
) default character set utf8mb4;
insert into demo_varchar(name) values ('张三');
-
示例:varchar(20),存入张三,utf8mb4:2 个中文占 6 字节 + 1 字节长度标记,一共 7 字节。
优点:省空间
缺点:需要记录长度,读取速度略慢于 char
适合:长短不固定:姓名、地址、标题、备注
注意:
char(11)、varchar(11) 括号里的数字,代表字符数量,不是字节数量
utf8mb4 里不同字符占用的字节不一样:
-
英文字母
a:1 个字符,占 1 字节 -
中文
你:1 个字符,占 3 字节 -
emoji 😀:1 个字符,占 4 字节
name varchar(2);
含义:最多允许存 2 个字符,不是 2 个字节。 可以存入:
ab→ 2 字符,一共 2 字节小明→ 2 字符,一共 6 字节😀a→ 2 字符,一共 4+1=5 字节
这三条都合法,都满足「最多 2 个字符」的限制。
整张行所有字段加起来总字节不能超过 65535 字节,是行上限,不是单个varchar字段。
这是 InnoDB 行的最大行大小限制 ,MySQL 层的定义:一行记录(除去 text、blob 这类溢出字段)的总存储字节上限是 65535 字节(2¹⁶−1)
text / tinytext / blob这类大字段不计入这个行长度 它们在本行里面只存一个很小的指针(20 字节左右),真实数据放到行外溢出页,所以不受这个 65535 限制。比如下面两个字段,单独看都不大,加起来就炸:
create table test_sum( a varchar(10000), b varchar(10000) ) default character set utf8mb4;utf8mb4,最坏情况:10000**4 + 10000**4 = 80000 >65535 → 建表报错。
3.text 系列
表格(长度前缀 + 最大容量,单位:字节)
表格
| 类型 | 长度前缀 | 最大容量 |
|---|---|---|
| tinytext | 1 字节 | 255 B |
| text | 2 字节 | 65535 B(≈64KB) |
| mediumtext | 3 字节 | 16777215 B(≈16MB) |
| longtext | 4 字节 | 4294967295 B(≈4GB) |
基础语法 demo
create table demo_text (
id int primary key auto_increment,
t1 tinytext,
t2 text,
t3 mediumtext,
t4 longtext
) default character set utf8mb4;
insert into demo_text(t1, t2) values
('简短备注', '一大段文章正文......');
重点特性
- 不能像 varchar 那样,用括号限制存储上限
text(200)语法允许写,但不是限制最多存 200 字符 ,只是客户端展示宽度,没有存储约束,开发不要这么写。varchar(200)的 200 是硬上限,这是巨大区别。 - 不计入 65535 行大小限制(InnoDB) 普通字段(char、varchar、int)的最大字节总和不能超过 65535; text 在本行里面只存一个很小的溢出指针,真正文本数据放到单独的溢出页。 所以存很长的内容,选 text,绕开 65535 的限制。 代价:读取的时候,需要额外一次 IO 读取溢出页,性能比 varchar 差。
四.日期时间类型
一共 5 种:date、time、datetime、timestamp、year
表格
| 类型 | 占用字节 | 格式 | 取值范围 | 核心特点 |
|---|---|---|---|---|
| date | 3 | YYYY-MM-DD |
1000-01-01 ~ 9999-12-31 | 只存日期,没有时分秒 |
| time | 3 | HH:MM:SS |
-838:59:59 ~ 838:59:59 | 可以表示时长,支持负数、大于 24 小时 |
| datetime | 8 | YYYY-MM-DD HH:MM:SS |
1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 | 日期 + 时间,不做时区转换 |
| timestamp | 4 | YYYY-MM-DD HH:MM:SS |
1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC | 底层 UTC 时间,读写自动时区转换,存在 2038 溢出问题 |
1. date
只存年月日,没有时分秒。 demo:
create table demo_date (
id int primary key auto_increment,
birthday date
);
insert into demo_date(birthday) values ('2026-05-20');
适用:生日、下单日期,不需要几点几分。
2. time
不只是一天内的时刻,用来记录时间间隔。 可以是负数、超过 24 小时。 demo:
create table demo_time (
id int primary key auto_increment,
spend_time time
);
insert into demo_time(spend_time) values ('25:00:00'), ('-01:30:00');
适用:视频时长、耗时。
3. datetime
保存的就是你看到的那个年月日时分秒文本,不带时区换算,时区改了,时间数字不变。
datetime 执行插入:
insert into t (dt) values ('2026-11-04 21:00:00');
硬盘里面老老实实存的就是一串字面:2026-11-04 21:00:00,就算时区改了也是这个值
常见用法:
create_time datetime default current_timestamp,
update_time datetime default current_timestamp on update current_timestamp
//`on update`:**当这条记录被更新的时候触发**。
1.create_time:记录创建时间
- 新增一行的时候,你不填这个字段,mysql 自动填上现在的时间
- 之后不管你怎么改这条数据,它永远不变
2.update_time:记录最后修改时间
- 新增一行的时候,不填,自动填现在的时间
- 只要你修改这一行任意别的字段,它自动刷新成刚刚修改的时间
- 如果你 update 了,但是数据和原来一模一样,它不会变
这个语法 MySQL5.6 及以后才支持 datetime 用 on update;更早版本只有 timestamp 支持。
完整 demo,直接复制运行
-- 如果表存在就删掉
drop table if exists test_time;
-- 建表
create table test_time (
id int primary key auto_increment,
name varchar(20),
create_time datetime default current_timestamp,
update_time datetime default current_timestamp on update current_timestamp
);
-- 插入数据,我们不写时间字段,让它自动生成
insert into test_time(name) values ('张三');
-- 查询,看两个时间是一样的
select * from test_time;
插入结果示例:
表格
| id | name | create_time | update_time |
|---|---|---|---|
| 1 | 张三 | 2026-11-04 17:30:00 | 2026-11-04 17:30:00 |
-- 更新 name,触发 on update
update test_time set name = '张三改名字了' where id=1;
-- 再次查询
select * from test_time;
更新后结果:
表格
| id | name | create_time | update_time |
|---|---|---|---|
| 1 | 张三改名字了 | 2026-11-04 17:30:00 | 2026-11-04 17:32:10 |
看到区别: create_time 保持最开始插入的时间不变 update_time 自动变成刚刚更新的时间
4. timestamp
底层存 UTC 时间戳,读写的时候自动根据当前会话时区转换。
create table demo_ts (
id int primary key auto_increment,
ts timestamp default current_timestamp on update current_timestamp
);
大坑: 上限到 2038-01-19,时间到了之后溢出报错;修改数据库时区,查询出来的时间跟着变。新项目尽量不用。
区分重点: datetime:存什么,查出来就是什么,时区改了不受影响。 timestamp:存 UTC,展示的时候换算成当前时区。
MySQL8.0 默认开启了 explicit_defaults_for_timestamp=ON,不再自动给 timestamp 加上 ON UPDATE CURRENT_TIMESTAMP!即更新的时候时间戳不会自动补上当前时间,必须手写声明,才会更新!
t3 timestamp default current_timestamp on update current_timestamp
五、enum和 set
enum 与 set 都是字符串类型,但它们的取值范围由建表时定义的选项决定。
1. enum 枚举(单选)
语法:enum('值1','值2','值3') 含义:只能选里面其中一个,不能多个
drop table if exists test_enum;
create table test_enum (
id int primary key auto_increment,
gender enum('男','女','保密')
);
-- ✅ 合法
insert into test_enum(gender) values ('男');
insert into test_enum(gender) values ('保密');
-- ❌ 非法,不在列表里,默认会变成空字符串(严格模式下直接报错)
-- insert into test_enum(gender) values ('外星人');
select * from test_enum;
特点:
- 单选,只能存一个
- 底层存数字:
'男'=1,'女'=2,'保密'=3,节省空间 - 适合:性别、状态(启用 / 禁用)
2. set 集合(多选)
语法:set('值1','值2','值3') 含义:可以选一个或者多个,多个用逗号隔开,不能有空格
drop table if exists test_set;
create table test_set (
id int primary key auto_increment,
hobby set('篮球','游戏','读书','跑步')
);
-- ✅ 选1个
insert into test_set(hobby) values ('游戏');
-- ✅ 选多个!逗号分隔,中间不要空格
insert into test_set(hobby) values ('游戏,读书');
insert into test_set(hobby) values ('篮球,跑步,读书');
-- ❌ 错误写法(逗号后面带空格)
-- insert into test_set(hobby) values ('游戏, 读书');
select * from test_set;
特点:
- 多选,最多可以组合里面任意几项
- 底层用位运算存储,非常省空间
- 适合:爱好、标签、权限
3. 对比表
| 类型 | 选择 | 存储 | 典型场景 |
|---|---|---|---|
| enum | 只能单选 | 数字 | 性别、订单状态 |
| set | 支持多选 | 位图 | 爱好、标签、权限 |
4. 重要坑点
- set 多个值中间逗号不能加空格
'游戏,读书'✅ |'游戏, 读书'❌ - 严格模式
sql_mode='STRICT_TRANS_TABLES'下,传非法值直接报错;非严格模式会截断 / 存空 - 不推荐大量业务使用:
- 新增选项需要
alter table修改字段,改动麻烦 - 程序里不好维护,一般开发中更常用单独字典表代替 enum /set
- 新增选项需要
5. 查询小 demo(set 专属)
find_in_set() 专门查 set 里面有没有某个值
-- 查出爱好包含读书的行
select * from test_set where find_in_set('读书', hobby);
--只是读书
select * from test_set where hobby = '读书';
