前言
MySQL 的约束和索引是面试、日常开发中最基础也最容易踩坑的知识点。很多人在建表时只写列级约束,分不清 UNIQUE 约束和唯一索引的区别,不了解 CHECK 在 5.7 下形同虚设,更不知道建表阶段就能直接创建普通索引。
这篇文章会把 六种约束的列级/表级写法、CHECK 的版本坑、外键生产禁用原因、索引的建表创建方式、以及面试高频考点 一次性讲清楚。每个结论都经过实际验证,拿来即用。
前置重要修正 & 说明
NOT NULL、DEFAULT仅支持列级,不存在表级写法
PRIMARY KEY / UNIQUE / CHECK / FOREIGN KEY支持列级、表级两种写法标准SQL条件使用
AND,禁止使用&&(&&是MySQL方言,不通用)MySQL 5.7 语法上允许写CHECK,但是完全不生效;8.0版本才真正实现CHECK约束
⚠️ 区分两个语法:
CONSTRAINT 名称 UNIQUE(字段)→ 标准SQL【唯一约束】
UNIQUE INDEX 索引名(字段)→ MySQL扩展【唯一索引】;底层存储结构一致,但DDL删除语法不同
一、约束的两种基础模板
MySQL 建表时,约束可以跟在字段后面(列级),也可以在所有字段定义完后统一声明(表级)。
-- 【列级约束】约束紧跟字段定义,只作用于当前字段
CREATE TABLE demo(
字段名 数据类型 [约束],
...
);
-- 【表级约束】所有字段定义完成后单独声明,支持多字段联合约束,可自定义约束名称
CREATE TABLE demo(
字段名 数据类型,
...,
[CONSTRAINT 约束名] 约束类型 (字段列表)
);
为什么要学表级写法?
- 联合主键、联合唯一、联合外键只能通过表级实现;
- 自定义约束名,后续
ALTER TABLE增删约束时更清晰,避免系统生成乱码名。
二、支持列级 & 表级写法的约束
1. PRIMARY KEY 主键约束
作用:非空 + 唯一 ,唯一标识一行。一张表最多只能有一个主键。主键约束内置两条强制规则: ① 字段不能为 NULL② 字段不能重复 数据库引擎执行写入时,会主动校验唯一性,重复数据拒绝入库。
① 列级写法(单列主键常用)
CREATE TABLE tb_user(
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
name VARCHAR(20)
);
② 表级写法(推荐,支持联合主键)
CREATE TABLE tb_user(
id INT AUTO_INCREMENT COMMENT '主键ID',
name VARCHAR(20),
PRIMARY KEY (id)
);
-- ✅ 联合主键:只能使用表级写法
CREATE TABLE tb_order_item(
order_id INT COMMENT '订单编号',
product_id INT COMMENT '商品编号',
PRIMARY KEY (order_id, product_id)
);
💡 主键自动生成聚簇索引(InnoDB),值不能为NULL,不能重复。MySQL 规定:主键约束的系统固定名称永远是 PRIMARY,自己命名是无效的! 哪怕你写 CONSTRAINT pk_tbuser_id PRIMARY KEY(id),去查约束名称依然是 PRIMARY,自定义名称直接被数据库忽略。
2. UNIQUE 唯一约束
作用:字段值不能重复,但允许多个 NULL(NULL和任何值都不相等)。
① 列级写法
CREATE TABLE tb_user(
id INT PRIMARY KEY AUTO_INCREMENT,
phone VARCHAR(11) UNIQUE COMMENT '手机号,唯一不可重复'
);
② 表级写法(联合唯一、自定义约束名)
CREATE TABLE tb_user(
id INT PRIMARY KEY AUTO_INCREMENT,
phone VARCHAR(11),
-- 标准SQL唯一约束
CONSTRAINT uk_tbuser_phone UNIQUE (phone)
);
-- 联合唯一
CONSTRAINT uk_phone_name UNIQUE (phone, name)
⚠️ 区分:
CONSTRAINT uk_xxx UNIQUE(字段)------ 标准唯一约束,删除用DROP CONSTRAINT uk_xxx;
UNIQUE INDEX uk_xxx(字段)------ MySQL 唯一索引,删除用DROP INDEX uk_xxx。两者底层结构一样,但DDL语法不同。
3. CHECK 检查约束
作用:限制字段取值范围。
🔴 重大坑 :MySQL 5.7 能解析 CHECK 语法但完全不生效,8.0 才真正支持。
① 列级写法
CREATE TABLE tb_user(
id INT PRIMARY KEY AUTO_INCREMENT,
age INT CHECK (age > 0 AND age <= 120) -- 5.7无效,8.0生效
);
② 表级写法(方便命名、后续删除)
CREATE TABLE tb_user(
id INT PRIMARY KEY AUTO_INCREMENT,
age INT,
CONSTRAINT ck_tbuser_age CHECK (age > 0 AND age <= 120)
);
4. FOREIGN KEY 外键约束
作用:保证表间参照完整性,仅 InnoDB 引擎支持。
⚠️ 生产环境几乎不用外键 :高并发下容易死锁、锁等待,实际项目都在业务层代码控制关联关系。
前置条件:被引用字段必须是主键或唯一索引,且数据类型严格一致。
① 列级写法(不推荐)
CREATE TABLE tb_order(
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT FOREIGN KEY REFERENCES tb_user(id)
);
② 表级写法(工程规范写法)
CREATE TABLE tb_order(
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT COMMENT '关联用户id',
CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES tb_user(id)
ON DELETE SET NULL -- 主表删除,子表置空
ON UPDATE CASCADE -- 主表主键更新,子表同步
);
三、仅支持列级写法的约束(无表级语法)
这两个约束只能紧跟在字段后面,写成表级会直接报错。
1. NOT NULL 非空约束
-- ✅ 正确
name VARCHAR(20) NOT NULL;
-- ❌ 不存在这种表级写法
CREATE TABLE tb_user(
id INT,
name VARCHAR(20),
NOT NULL(name) -- 语法错误
);
2. DEFAULT 默认值约束
-- ✅ 正确
status TINYINT DEFAULT 1 COMMENT '状态 1启用 0禁用';
-- ❌ 非法表级写法
CREATE TABLE tb_user(
status TINYINT,
DEFAULT(status) 1 -- 语法错误
);
四、约束汇总对照表
| 约束类型 | 列级支持 | 表级支持 | 支持多字段联合 | 说明 |
|---|---|---|---|---|
| PRIMARY KEY | ✅ | ✅ | ✅ | 一张表最多1个,自动创建聚簇索引 |
| UNIQUE | ✅ | ✅ | ✅ | 允许多个NULL,一张表可有多个 |
| CHECK | ✅ | ✅ | ✅ | MySQL5.7无效,8.0才生效 |
| FOREIGN KEY | ✅ | ✅ | ✅ | 仅InnoDB支持,生产不建议用 |
| NOT NULL | ✅ | ❌ | - | 仅列级 |
| DEFAULT | ✅ | ❌ | - | 仅列级 |
五、完整可运行的规范建表示例
CREATE TABLE tb_user (
id INT AUTO_INCREMENT COMMENT '主键ID',
username VARCHAR(20) NOT NULL COMMENT '用户名',
age INT COMMENT '年龄',
phone VARCHAR(11) NOT NULL COMMENT '手机号码',
status TINYINT DEFAULT 1 COMMENT '状态:1正常 0禁用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-- 表级约束区域
PRIMARY KEY (id),
CONSTRAINT uk_tbuser_phone UNIQUE (phone),
CONSTRAINT ck_tbuser_age CHECK (age > 0 AND age <= 120)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户信息表';
六、ALTER TABLE 动态增删约束(面试高频)
-- 1. 添加唯一约束
ALTER TABLE tb_user ADD CONSTRAINT uk_tbuser_phone UNIQUE (phone);
-- 删除唯一约束
ALTER TABLE tb_user DROP CONSTRAINT uk_tbuser_phone;
-- 2. 添加检查约束
ALTER TABLE tb_user ADD CONSTRAINT ck_tbuser_age CHECK (age > 0 AND age <= 120);
-- 删除检查约束
ALTER TABLE tb_user DROP CONSTRAINT ck_tbuser_age;
-- 3. 添加外键
ALTER TABLE tb_order ADD CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES tb_user(id);
-- 删除外键(注意语法是 DROP FOREIGN KEY)
ALTER TABLE tb_order DROP FOREIGN KEY fk_order_user;
-- 4. 添加非空(本质是修改字段属性)
ALTER TABLE tb_user MODIFY COLUMN username VARCHAR(20) NOT NULL;
-- 5. 修改默认值
ALTER TABLE tb_user ALTER COLUMN status SET DEFAULT 1;
七、建表时直接创建索引(不仅可以在建表后加)
很多人以为索引只能在表创建后用 ALTER TABLE ... ADD INDEX,其实建表时完全可以直接定义所有索引,这也是上线前的最佳实践。
1. 约束自动附带索引
-
主键约束 → 自动创建主键索引
-
唯一约束 → 自动创建唯一索引
CREATE TABLE tb_user (
id INT AUTO_INCREMENT PRIMARY KEY, -- 自动主键索引
phone VARCHAR(11) UNIQUE -- 自动唯一索引
);
2. 表级手动创建普通/复合索引(最常用)
CREATE TABLE tb_user (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
age INT,
-- 单列普通索引
INDEX idx_tbuser_age (age),
-- 复合(联合)索引
INDEX idx_tbuser_name_age (name, age)
);
KEY是INDEX的同义词,写哪个都可以。
3. 列级索引(不推荐)
name VARCHAR(20) NOT NULL INDEX -- 无法自定义索引名,维护困难
4. 建表后再追加索引
ALTER TABLE tb_user ADD INDEX idx_tbuser_age(age);
-- 或
CREATE INDEX idx_tbuser_age ON tb_user(age);
大表加索引会锁表(5.7),生产环境建议使用
pt-online-schema-change或gh-ost。
八、约束 vs 普通索引的关键区分
- 主键/UNIQUE 约束 :目的是保证数据唯一性,底层顺便帮你建了索引。
- 普通 INDEX 索引 :没有任何校验规则,唯一目的就是加速查询。
UNIQUE约束 ≠ 普通索引,前者多了唯一性检查。- 删除区别:
- 约束用
DROP CONSTRAINT; - 索引用
DROP INDEX。
- 约束用
九、面试高频总结背诵点
NOT NULL、DEFAULT只能列级;主键、唯一、CHECK、外键两种写法均可。- 联合主键、联合唯一必须用表级写法。
- MySQL 5.7 的 CHECK 只是摆设,8.0 才真正生效。
- 外键仅 InnoDB 支持,生产环境用代码控制关联,不用数据库外键。
CONSTRAINT 约束名用于给约束命名,方便后续按名称删除;列级约束会由系统自动生成随机名,不便于维护。- 建表时就可以创建索引 ,通过表级
INDEX(字段)定义普通索引,主键/唯一约束自动生成索引。 - 已存在的表用
ALTER TABLE ADD INDEX或CREATE INDEX追加索引,但大表要注意锁表风险。