常用数据类型与约束
🎯 引言
前面我们已经会建表和查询了,但建表时每个字段后面跟的 INT、VARCHAR 到底该怎么选?选错了会有什么后果?学完这篇文章,你能说清楚常用数据类型的区别和适用场景,会用约束给表加上「防护规则」,并独立设计出一张结构合理的用户表。
🧱 数值类型:INT 与 DECIMAL
整数最常用的就是 INT,大约能存正负 21 亿,存用户 ID、年龄、库存都够用。
小数方面,MySQL 提供 FLOAT、DOUBLE 和 DECIMAL 三种。这里有一个重要的坑:FLOAT 和 DOUBLE 是浮点数,存在精度误差。
你在 JS 里可能遇到过 0.1 + 0.2 不等于 0.3,而是 0.30000000000000004。这是二进制浮点数的通病,MySQL 的 FLOAT 也一样。如果只是展示温度、评分,这点误差不影响;但金额差一分钱都是事故,所以涉及钱的字段一律用 DECIMAL,它按十进制精确存储。
CREATE TABLE account (
id INT PRIMARY KEY,
balance DECIMAL(10, 2)
);
DECIMAL(10, 2) 表示一共 10 位数字,其中 2 位是小数,也就是最大能存 99999999.99。
⚡ 字符串类型:CHAR、VARCHAR 与 TEXT
CHAR 是定长字符串,CHAR(11) 不管你存几个字,都按 11 个字符占位,适合长度固定的数据,比如手机号、MD5 值。
VARCHAR 是变长字符串,VARCHAR(50) 存多长占多长,只在尾部多记 1 到 2 个字节的长度信息。用户名、邮箱、标题这类长度不一的字段都用它。
可以打个比喻:CHAR 像固定大小的快递纸箱,不管装什么都占那么大地方;VARCHAR 像可伸缩的袋子,装多少占多少。
TEXT 用于长文本,比如文章内容、商品详情,能存几万字符。但它不适合做条件查询和索引,所以「经常要拿来搜索的字段」不要用 TEXT。
CREATE TABLE note (
id INT PRIMARY KEY,
title VARCHAR(100),
content TEXT
);
🧰 日期时间类型:DATE、DATETIME 与 TIMESTAMP
- DATE:只存日期,格式
2026-07-20,适合生日、节假日这类不需要时间的场景。 - DATETIME:存日期加时间,格式
2026-07-20 10:30:00,范围大(到 9999 年),适合注册时间、订单时间等业务时间。 - TIMESTAMP:也存日期加时间,但内部会跟随时区转换,范围只到 2038 年。它适合记录「数据变动的时刻」,比如更新时间。
日常开发中,业务时间字段(注册时间、创建时间)用 DATETIME 是常见选择,简单直观、不受 2038 年限制。
SELECT NOW(), CURDATE(), CURTIME();
这条语句能查看当前的完整时间、日期和时刻,在插入数据时也常用 NOW() 自动填当前时间。执行结果如下(示例,实际以你执行时的时间为准):
| NOW() | CURDATE() | CURTIME() |
|---|---|---|
| 2026-07-20 10:30:00 | 2026-07-20 | 10:30:00 |
💡 布尔值:用 TINYINT(1)
MySQL 没有真正的布尔类型,约定用 TINYINT(1) 表示:1 代表真,0 代表假。
比如用户表里标记是否会员、是否删除(软删除),都是典型用法:
is_member TINYINT(1) DEFAULT 0
虽然 MySQL 也认识 BOOLEAN 关键字,但它只是 TINYINT(1) 的别名,两者完全一样。
🛠 常用约束:给表加防护规则
约束是建表时声明的规则,MySQL 会在写入数据时自动检查,不满足就拒绝。这就像小区门口的保安,不合格的数据根本进不了库,比在代码里到处写校验可靠得多。
常用的有这几个:
- PRIMARY KEY:主键,唯一标识每一行,一张表通常有一个,不能重复也不能为空。
- AUTO_INCREMENT:自增,配合主键使用,插入时不用手动给 ID,MySQL 自动 +1。
- NOT NULL:非空,该字段必须填,防止插入缺斤少两的数据。
- UNIQUE:唯一,同一个值只能出现一次,比如用户名不能撞车。
- DEFAULT:默认值,插入时没给这个字段赋值,就自动填默认值。
把这些约束组合起来,一张规范的用户表就出来了:
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
age INT DEFAULT 18,
city VARCHAR(50) DEFAULT '未知',
is_member TINYINT(1) DEFAULT 0,
created_at DATETIME DEFAULT NOW()
);
插入一条数据试试,只给必填的字段赋值,其他交给默认值和自增:
INSERT INTO user (username) VALUES ('小明');
SELECT * FROM user;
执行结果如下(created_at 实际以你插入时的时间为准):
| id | username | age | city | is_member | created_at |
|---|---|---|---|---|---|
| 1 | 小明 | 18 | 未知 | 0 | 2026-07-20 10:30:00 |
可以看到 id 自动是 1,age 自动是 18,created_at 自动是当前时间。如果再插入一次 username 为「小明」的记录,会直接报错,这就是 UNIQUE 约束在起作用。
user 表,可以先执行 DROP TABLE IF EXISTS user; 再重新创建。⚡ 改表结构:ALTER TABLE
表建好之后想加一列、删一列怎么办?不用删表重建,用 ALTER TABLE 就行:
ALTER TABLE user ADD email VARCHAR(100);
ALTER TABLE user DROP COLUMN email;
这两条分别是给 user 表加一个 email 列、再把它删掉。改表结构还有很多玩法,初学阶段知道有这回事、会加列删列就够用了。
💡 常见场景选型速查
| 场景 | 推荐类型 | 说明 |
|---|---|---|
| 用户 ID | INT AUTO_INCREMENT | 主键自增,不用手动维护 |
| 用户名 | VARCHAR(50) | 长度不一,加 UNIQUE 防重复 |
| 手机号 | CHAR(11) | 长度固定,定长存储 |
| 余额 | DECIMAL(10, 2) | 金额必须精确,不能用 FLOAT |
| 文章内容 | TEXT | 长文本,不参与条件查询 |
| 是否会员 | TINYINT(1) | 1 是真,0 是假 |
| 注册时间 | DATETIME | 业务时间,范围大不受限 |
🧾 小节总结
- 整数用
INT;金额等精确小数用DECIMAL,FLOAT有浮点精度误差,和 JS 里0.1 + 0.2的问题同源。 - 字符串:定长用
CHAR(手机号),变长用VARCHAR(用户名),长文本用TEXT。 - 日期时间:只用日期选
DATE,业务时间常用DATETIME,TIMESTAMP跟随时区且只到 2038 年。 - 布尔值用
TINYINT(1),1为真、0为假。 - 约束是写入时的自动校验:
PRIMARY KEY、AUTO_INCREMENT、NOT NULL、UNIQUE、DEFAULT常组合使用。 - 表结构可以用
ALTER TABLE随时加列、删列,不必删表重建。
❓ 知识问答
Q1:金额为什么不能用 FLOAT?
FLOAT 是二进制浮点存储,会有精度误差,就像 JS 里 0.1 + 0.2 不等于 0.3 一样。金额差一分钱都可能引发对账问题,所以要用精确存储的 DECIMAL。
Q2:VARCHAR(50) 的 50 是字节还是字符?
在 MySQL 8.x 默认的 utf8mb4 字符集下是 50 个字符,中英文都按一个字符算,不用担心中文占多字节的问题。
Q3:手机号是数字,为什么不用 INT 存?
手机号可能以 0 开头,也不需要做加减运算,而且 11 位数字加区号可能超出 INT 范围。凡是「不参与计算的数字串」,比如手机号、身份证号,都应该用字符串存。
Q4:主键必须是自增数字吗?
不必须,主键的核心要求是「唯一且不为空」。实际项目里也有用 UUID 或雪花 ID 做主键的,但初学阶段自增 INT 简单直观,是常见选择。
Q5:NOT NULL 和 DEFAULT 必须一起用吗?
不是,但经常配合。NOT NULL 要求必须填,DEFAULT 负责「没填时自动补一个值」。两者一起用,插入数据时就能少写很多字段。
🧪 小练习
练习 1:设计一张 product 商品表,包含:自增主键 id、商品名 name(变长字符串,必填且唯一)、价格 price(精确到分)、库存 stock(整数,默认 0)、上架时间 created_at(默认当前时间)。写出完整建表语句。
-- 请在这里编写 SQL
练习 2:给上一题的 product 表加一个 category 分类列(VARCHAR(50)),然后插入两条商品数据(价格分别带小数),最后查询整张表验证结果。
-- 请在这里编写 SQL
🎉 恭喜你已经掌握 MySQL 常用数据类型与约束啦!下一篇我们学习多表关联查询,看看如何用 JOIN 把用户表和文章表连起来一起查。
