MySQL 单表约束超详细教程:主键、非空、唯一、默认值一次讲透
很多人学 MySQL 约束时,第一反应是“这不就是几个关键字吗”。 PRIMARY KEY、NOT NULL、UNIQUE、DEFAULT,语法看一遍就会了。
但真正容易出问题的地方,不在于你会不会写,而在于你到底知不知道它们分别约束了什么、能不能一起用、对插入数据有什么影响、报错时该怎么判断。
单表设计里,这四类约束几乎是最基础的一层防线。表结构如果这一层就松了,后面业务代码写得再认真,脏数据还是会进来。
这篇文章就把 MySQL 单表里最常见的 4 种约束一次讲透:主键、非空、唯一、默认值。内容尽量不绕,重点放在实际建表、插入数据、报错现象和常见误区上。
为什么单表约束一定要学明白
约束的本质不是“限制你写 SQL”,而是“限制错误数据进入表”。
比如下面这些问题,单靠应用层校验并不稳:
- 用户表允许两个完全相同的用户名
- 订单表主键为空
- 手机号字段被插入了
NULL - 状态字段没人传值,结果整列都是空的
- 插入一批数据时,漏传某个字段导致业务状态异常
这些问题只要落库,后面清理成本就比建表时多得多。
所以别把约束当语法题,它本质上是数据质量控制。
先准备一张测试表
为了把四类约束拆开讲,先准备一张示例表。下面这张表故意把几种常见约束都放进去:
CREATE TABLE employee (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(20) NOT NULL,
emp_email VARCHAR(50) UNIQUE,
emp_salary DECIMAL(10, 2) DEFAULT 0.00,
emp_status VARCHAR(10) DEFAULT '在职'
);
字段说明:
emp_id:员工编号,主键emp_name:员工姓名,不能为空emp_email:邮箱,不能重复emp_salary:工资,默认值0.00emp_status:员工状态,默认值'在职'
先看表结构:
DESC employee;
你会看到对应字段的 Null、Key、Default 等信息,这就是约束落地后的直接体现。
1. 主键约束 PRIMARY KEY
主键是表中用于唯一标识一行数据的字段。
说得直白一点: 一张表里,主键列的值不能重复,也不能为 NULL。每一行都必须能靠主键精准找到。
主键的特点
主键约束有两个核心特征:
- 值唯一
- 不能为
NULL
这也是为什么很多人会说:主键 = 唯一 + 非空
但它又不只是“唯一非空”这么简单。主键通常承担“记录身份标识”的角色,而普通唯一约束更多只是“这个字段不能重复”。
创建主键的写法
最常见的列级写法:
CREATE TABLE student (
id INT PRIMARY KEY,
name VARCHAR(20)
);
也可以用表级写法:
CREATE TABLE student (
id INT,
name VARCHAR(20),
PRIMARY KEY (id)
);
这两种在单字段主键场景下都可以。
主键插入测试
先插入一条正常数据:
INSERT INTO employee (emp_id, emp_name, emp_email, emp_salary, emp_status)
VALUES (1, '张三', 'zhangsan@example.com', 8000.00, '在职');
再插入一条主键重复的数据:
INSERT INTO employee (emp_id, emp_name, emp_email, emp_salary, emp_status)
VALUES (1, '李四', 'lisi@example.com', 9000.00, '在职');
这时会报错,原因很明确:emp_id=1 已经存在了。
再试一下主键为 NULL:
INSERT INTO employee (emp_id, emp_name)
VALUES (NULL, '王五');
也会报错。因为主键天然不允许为空。
主键最容易犯的错
误区 1:把有业务意义的字段直接当主键
比如手机号、身份证号、邮箱,看起来都“唯一”,但未必适合做主键。 原因很简单:这些字段虽然可能唯一,但一旦业务规则变化,修改成本很高。
更稳的做法通常是:
- 用一个独立的
id作为主键 - 业务字段再单独加唯一约束
这才是更常见、也更抗变化的设计。
误区 2:认为一张表可以有多个主键
不行。 一张表只能有一个主键。
注意,“一个主键”不等于“只能一个字段”。 如果是联合主键,那本质上也还是一个主键,只不过由多个列组成。
但这篇文章讲的是单表基础约束,单列主键先学透更重要。
2. 非空约束 NOT NULL
NOT NULL 的作用很直接:这个字段必须有值,不能插入 NULL。
很多初学者会把“空字符串”和 NULL 混为一谈,这是最常见的坑之一。 它们不是一回事。
NULL:表示没有值、未知'':表示空字符串,依然是一个值
NOT NULL 约束禁止的是 NULL,不是空字符串。
创建非空约束的写法
CREATE TABLE customer (
id INT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
phone VARCHAR(20)
);
这里 name 列不能为空,phone 可以为空。
非空约束插入测试
插入正常数据:
INSERT INTO employee (emp_id, emp_name)
VALUES (2, '李四');
插入 NULL:
INSERT INTO employee (emp_id, emp_name)
VALUES (3, NULL);
这时会报错,因为 emp_name 设置了 NOT NULL。
NOT NULL 和默认值不是一回事
这一点很多人会混。
看这个字段:
emp_status VARCHAR(10) DEFAULT '在职'
它有默认值,但没有写 NOT NULL。 这意味着什么?
意味着:
- 你插入时不写这个字段,MySQL 会帮你填默认值
'在职' - 但你如果明确写了
NULL,能不能成功,要看字段是否允许NULL
也就是说:
DEFAULT负责“没传时用什么”NOT NULL负责“能不能是 NULL”
这两个经常一起出现,但控制的是两件不同的事。
非空约束常见误区
误区 1:字段重要就一定要 NOT NULL
不一定。
有些字段天然就是可缺失的,比如:
- 备注
- 第二联系方式
- 离职时间
- 头像地址
这些字段如果业务上允许暂时没有值,就没必要强行加 NOT NULL。 约束是为业务服务,不是为了把表结构写满。
误区 2:NOT NULL 能防止空内容
防不了。 下面这种写法并不会报错:
INSERT INTO customer (id, name)
VALUES (1, '');
因为 '' 不是 NULL。
如果你业务上要求“名字不能为空字符串”,那要靠:
- 应用层校验
- 或者更严格的数据库约束设计
单靠 NOT NULL 不够。
3. 唯一约束 UNIQUE
UNIQUE 用来保证字段值不重复。
这类约束非常适合放在“业务上要求唯一”的字段上,比如:
- 用户名
- 邮箱
- 学号
- 工号
- 订单编号
创建唯一约束的写法
列级写法:
CREATE TABLE account (
id INT PRIMARY KEY,
username VARCHAR(20) UNIQUE,
password VARCHAR(50) NOT NULL
);
表级写法:
CREATE TABLE account (
id INT PRIMARY KEY,
username VARCHAR(20),
password VARCHAR(50) NOT NULL,
UNIQUE (username)
);
唯一约束插入测试
先插入一条正常数据:
INSERT INTO employee (emp_id, emp_name, emp_email)
VALUES (4, '王五', 'wangwu@example.com');
再插入相同邮箱:
INSERT INTO employee (emp_id, emp_name, emp_email)
VALUES (5, '赵六', 'wangwu@example.com');
会报错,因为 emp_email 上有唯一约束。
唯一约束和主键有什么区别
这个问题几乎必问。
它们都能保证唯一,但侧重点不一样:
| 对比项 | PRIMARY KEY | UNIQUE |
|---|---|---|
| 是否唯一 | 是 | 是 |
是否允许 NULL |
否 | 一般允许 |
| 一张表可定义几个 | 只能一个 | 可以多个 |
| 主要用途 | 唯一标识一行记录 | 保证某字段值不重复 |
真正实战里,最常见的搭配是:
id做主键username、email做唯一约束
这套设计非常常见,也很稳。
UNIQUE 对 NULL 的理解要小心
这是一个很容易被忽略的点。
很多人会想: “唯一约束不允许重复,那多个 NULL 算不算重复?”
在 MySQL 里,通常多个 NULL 是可以存在于唯一列中的,因为 NULL 表示“未知值”,不按普通相等逻辑处理。
比如下面这种情况,往往是允许的:
INSERT INTO employee (emp_id, emp_name, emp_email)
VALUES (6, '小明', NULL);
INSERT INTO employee (emp_id, emp_name, emp_email)
VALUES (7, '小红', NULL);
如果你的业务要求“邮箱不仅不能重复,而且必须填写”,那就不能只写 UNIQUE,而应该写成:
emp_email VARCHAR(50) NOT NULL UNIQUE
这才完整。
唯一约束常见误区
误区 1:有唯一约束就不用主键
不行。
唯一约束和主键虽然都能防重复,但用途不同。 主键是整行记录的身份标识,唯一约束只是字段规则。
别拿 username 直接替代整表主键,后面大概率会难受。
误区 2:唯一字段一定适合做查询主条件
也不绝对。
唯一约束能保证值不重复,但是否适合作为高频查询条件,还要看业务模式和索引设计。 别看到 UNIQUE 就自动等于“这是最优查询列”。那是另外一个话题。
4. 默认约束 DEFAULT
DEFAULT 用于指定字段在未显式赋值时使用的默认值。
这一点很关键: 它只在“你没传这个字段”时生效,不是任何情况下都强制覆盖。
创建默认值的写法
CREATE TABLE orders (
id INT PRIMARY KEY,
order_no VARCHAR(30) NOT NULL UNIQUE,
status VARCHAR(20) DEFAULT '待支付',
amount DECIMAL(10, 2) DEFAULT 0.00
);
这里:
status默认是'待支付'amount默认是0.00
默认值插入测试
插入时省略带默认值的字段:
INSERT INTO employee (emp_id, emp_name, emp_email)
VALUES (8, '小刚', 'xiaogang@example.com');
这时:
emp_salary会自动取0.00emp_status会自动取'在职'
查询一下:
SELECT * FROM employee WHERE emp_id = 8;
你会看到默认值已经填上了。
如果显式传值,会覆盖默认值
INSERT INTO employee (emp_id, emp_name, emp_email, emp_status)
VALUES (9, '小丽', 'xiaoli@example.com', '离职');
这时 emp_status 就不会再用默认值 '在职',而是使用你传入的 '离职'。
DEFAULT 最容易误解的地方
误区 1:默认值等于自动修正错误数据
不是。
DEFAULT 只处理“未提供值”的情况。 如果你明确传了一个值,哪怕这个值不符合业务预期,只要不违反其他约束,数据库照样会收。
误区 2:传 NULL 就会自动变成默认值
不一定。这个得分情况。
如果字段允许 NULL,你显式传了 NULL,数据库通常会按 NULL 处理,而不是帮你改成默认值。
比如:
INSERT INTO employee (emp_id, emp_name, emp_email, emp_status)
VALUES (10, '老周', 'laozhou@example.com', NULL);
这里如果 emp_status 没有 NOT NULL,那它可能就是 NULL,而不是 '在职'。
这就是为什么很多字段需要写成:
status VARCHAR(20) NOT NULL DEFAULT '待支付'
少了 NOT NULL,默认值的约束力度其实没你想象中那么强。
5. 四种约束一起用时,怎么理解最清楚
很多人单独看每个约束都懂,一旦组合起来就开始乱。
其实可以这样记:
PRIMARY KEY:这行数据是谁NOT NULL:这个字段必须有值UNIQUE:这个字段值不能重复DEFAULT:你不传的时候,我给你补一个默认值
把它们放在一起看,就清楚了。
例如:
CREATE TABLE user_info (
user_id INT PRIMARY KEY,
username VARCHAR(20) NOT NULL UNIQUE,
gender CHAR(1) DEFAULT '男',
register_status VARCHAR(20) NOT NULL DEFAULT '正常'
);
这个表的约束逻辑是:
user_id:必须唯一,不能为空,标识一条用户记录username:必须填写,而且不能重名gender:可以不传,不传就默认'男'register_status:不传就默认'正常',但不能是NULL
这就是组合设计的实际意义: 每个字段根据业务语义,承担不同的数据规则。
6. 修改表时添加约束
有时候表已经建好了,后面才想补约束。这种情况也很常见。
添加非空约束
ALTER TABLE employee
MODIFY emp_name VARCHAR(20) NOT NULL;
添加唯一约束
ALTER TABLE employee
ADD UNIQUE (emp_email);
添加默认值
ALTER TABLE employee
MODIFY emp_status VARCHAR(10) DEFAULT '在职';
添加主键
ALTER TABLE employee
ADD PRIMARY KEY (emp_id);
但这里有个前提: 原表数据必须已经满足约束条件。
比如你想给 emp_email 加唯一约束,如果表里已经有重复邮箱,那这条 SQL 会直接失败。 这不是 MySQL 刁难你,是你历史数据已经脏了。
所以给旧表补约束,先查数据,再改结构,这个顺序别反。
7. 实战里最常见的几种约束设计
下面给几个很典型的字段设计方式。
主键 + 自增
id INT PRIMARY KEY AUTO_INCREMENT
这是最常见的主键设计。
非空 + 唯一
username VARCHAR(30) NOT NULL UNIQUE
表示用户名必填,且不能重复。
非空 + 默认值
status TINYINT NOT NULL DEFAULT 1
表示状态字段必须有值,没传时默认给 1。
唯一 + 可空
email VARCHAR(50) UNIQUE
表示邮箱不能重复,但允许暂时不填。
这几种组合,几乎在业务表里天天能见到。
8. 一张表里到底该怎么选约束
可以按下面这个思路判断:
哪些字段适合主键
- 能唯一标识一条记录
- 最好稳定,不轻易修改
- 通常选整数型主键更常见
哪些字段适合非空
- 业务上必须填写
- 缺失后会导致记录失去意义
- 后续逻辑强依赖该值存在
哪些字段适合唯一
- 业务规则要求不能重复
- 重复后会造成身份冲突或数据歧义
哪些字段适合默认值
- 大量数据插入时经常省略
- 存在合理初始值
- 不想每次插入都手写一遍
重点不是把所有字段都加满约束,而是让约束和业务含义对得上。
9. 常见面试题顺手总结
主键和唯一有什么区别
- 主键不能为
NULL,唯一约束一般可以 - 一张表只能有一个主键,但可以有多个唯一约束
- 主键主要用于标识记录,唯一约束主要用于限制字段不重复
NOT NULL 和 DEFAULT 有什么区别
NOT NULL:不允许字段为NULLDEFAULT:插入时如果没传值,就使用默认值
两者控制的不是一件事。
UNIQUE 能不能和 NOT NULL 一起用
可以,而且很常见。
username VARCHAR(20) NOT NULL UNIQUE
这表示用户名必须填写,且不能重复。
主键能不能重复、能不能为空
都不行。
10. 最后一段经验话
约束这块最忌讳的不是不会写,而是“看起来会,其实没分清边界”。
尤其是这几个点:
- 主键不是普通唯一字段
NOT NULL不等于禁止空字符串DEFAULT不是万能兜底UNIQUE遇到NULL时要特别小心- 给旧表加约束前,先检查历史数据
很多数据库问题不是复杂 SQL 搞出来的,而是表设计一开始就太松。 单表约束看起来基础,实际上就是数据质量的第一道门槛。
把这四个约束吃透,后面学外键、索引、表关系设计,思路会顺很多。