MySQL数据库约束:把“能插入”升级成“插入就对”

数据库约束一次讲清:把"能插入"升级成"插入就对"

数据库表如果没有约束,就像没有交通规则的十字路口:看起来自由,实际上全靠运气。约束做的事情很朴素:给表里的数据加规则,从源头保证准确性、可靠性与一致性。

本文把常见约束按"什么时候用、怎么建、违反会怎样"讲透,并且每个板块都配一条能直接跑的 SQL 例句。


1)约束到底是什么:给数据立规矩

来看一句定义:数据库约束是施加在表数据上的规则/条件,用来确保数据准确、可靠、相容。约束可以基于数据类型、取值范围、唯一性、非空等规则。

常见类型包括:NOT NULLDEFAULTUNIQUEPRIMARY KEYFOREIGN KEYCHECK


2)NOT NULL:非空约束(字段必须有值)

场景:姓名、标题、订单号这类字段,空值没有意义,就该禁止 NULL。

来看这段代码:给 name 加非空约束后,再插入 NULL 会直接报错。

sql 复制代码
DROP TABLE IF EXISTS student;
CREATE TABLE student (
  id   BIGINT,
  name VARCHAR(20) NOT NULL
);

INSERT INTO student VALUES (1, NULL);   -- 触发:Column 'name' cannot be null
INSERT INTO student VALUES (1, '张三'); -- 正常

顺手检查结构也很直观:DESC student;Null 列会显示 NO(不允许 NULL)或 YES(允许 NULL)。


3)DEFAULT:默认值约束(不赋值时给个"兜底值")

场景:年龄默认 18、状态默认"未支付"、创建时间默认当前时间等。

来看这段代码:age 设置默认 18,不提供 age 时自动填 18;但如果手动明确插入 NULL,依然会得到 NULL(默认值不会强行覆盖显式 NULL)。

sql 复制代码
DROP TABLE IF EXISTS student;
CREATE TABLE student (
  id   BIGINT,
  name VARCHAR(20) NOT NULL,
  age  INT DEFAULT 18
);

INSERT INTO student(id, name) VALUES (1, '张三');              -- age=18
INSERT INTO student(id, name, age) VALUES (2, '李四', NULL);   -- age=NULL(显式给 NULL)

DESC student;Default 列能看到默认值(例如 18)。


4)UNIQUE:唯一约束(值不能重复)

场景:学号、身份证号、邮箱、手机号------同一张表里不允许重复。

来看这段代码:sno 加唯一约束后,重复插入会报 Duplicate entry

sql 复制代码
DROP TABLE IF EXISTS student;
CREATE TABLE student (
  id   BIGINT,
  name VARCHAR(20) NOT NULL,
  age  INT DEFAULT 18,
  sno  VARCHAR(10) UNIQUE
);

INSERT INTO student(id, name, sno) VALUES (1, '张三', '100001');
INSERT INTO student(id, name, sno) VALUES (2, '李四', '100001'); -- 触发唯一冲突

结构里也能看出来:DESC student;Key 列会显示 UNI 表示唯一约束。


5)PRIMARY KEY:主键约束(每行的"身份证")

主键的硬规则很简单但很强:

  • 唯一标识每条记录
  • 不能重复
  • 不能为 NULL
  • 每张表只能有一个主键
  • 主键可以是单列多列(复合主键)
  • 通常建议每张表都有主键,且主键列常用 BIGINT

5.1 自增主键:让数据库维护主键值

来看这段代码:id 设置 PRIMARY KEY auto_increment,插入时不给 id 或给 NULL,数据库会自动生成。DESCExtra 显示 auto_increment

sql 复制代码
DROP TABLE IF EXISTS student;
CREATE TABLE student (
  id   BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20) NOT NULL,
  age  INT DEFAULT 18,
  sno  VARCHAR(10) UNIQUE
);

INSERT INTO student(id, name, sno) VALUES (NULL, '张三', '100001'); -- 自动生成 id
INSERT INTO student(name, sno) VALUES ('李四', '100002');          -- 自动生成 id

5.2 主键不一定连续:插入失败会"浪费号段"

来看这段代码:如果某次插入因为唯一冲突失败,已分配的自增 id 可能作废,导致后续 id 出现跳号。

sql 复制代码
INSERT INTO student(name, sno) VALUES ('王五', '100002');  -- sno 冲突,插入失败
INSERT INTO student(name, sno) VALUES ('王五', '100003');  -- 下一次成功插入,id 可能跳过

5.3 手动指定更大的主键:下一次自增从最大值继续

sql 复制代码
INSERT INTO student(id, name, sno) VALUES (100, '赵六', '100004');
INSERT INTO student(name, sno) VALUES ('钱七', '100005'); -- 自增会从 100 往后

5.4 主键冲突时"插入否则更新":ON DUPLICATE KEY UPDATE

来看这段代码:当插入触发主键/唯一键冲突时,转而执行更新,等效于对冲突行做 UPDATE ... WHERE key=...

sql 复制代码
INSERT INTO student(id, name, sno) VALUES (100, '赵六', '100100')
ON DUPLICATE KEY UPDATE name='赵六', sno='100100';

5.5 REPLACE:存在冲突就"替换",不存在就插入

REPLACE 的效果更像"先删后插":冲突时会影响 2 行(删除旧行 + 插入新行),不冲突时影响 1 行。

sql 复制代码
REPLACE INTO student(id, name, sno) VALUES (101, '钱七', '100101'); -- 冲突则替换
REPLACE INTO student(id, name, sno) VALUES (102, '吴八', '100102'); -- 不冲突则插入

5.6 只能有一个主键 & 复合主键

主键只能定义一次:两列都写 PRIMARY KEY 会直接报 "Multiple primary key defined"。

复合主键则由多列共同决定是否冲突:

sql 复制代码
DROP TABLE IF EXISTS student;
CREATE TABLE student (
  id   BIGINT,
  name VARCHAR(20),
  PRIMARY KEY (id, name)
);

INSERT INTO student(id, name) VALUES (1, '张三');
INSERT INTO student(id, name) VALUES (1, '张三'); -- 复合主键冲突
INSERT INTO student(id, name) VALUES (2, '张三'); -- 可插入(id 不同)

6)FOREIGN KEY:外键约束(维护表与表之间的关系)

外键用于定义**主表(父表)从表(子表)**之间的关联:外键定义在从表列上,引用主表的主键或唯一列。要求从表外键列的值要么在主表存在,要么为 NULL。

6.1 建两张表:class(主表)+ student(从表)

来看这段代码:student.class_id 引用 class.id

sql 复制代码
DROP TABLE IF EXISTS class;
CREATE TABLE class (
  id   BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20) NOT NULL
);

INSERT INTO class(name) VALUES ('java01'), ('java02'), ('java03');

DROP TABLE IF EXISTS student;
CREATE TABLE student (
  id       BIGINT PRIMARY KEY AUTO_INCREMENT,
  name     VARCHAR(20) NOT NULL,
  age      INT DEFAULT 18,
  class_id BIGINT,
  FOREIGN KEY (class_id) REFERENCES class(id)
);

DESC student;class_idKey 可能显示 MUL(文档示例中用于表示外键列)。

6.2 外键插入规则:主表不存在就失败,但 NULL 可以

sql 复制代码
INSERT INTO student(name, class_id) VALUES ('张三', 1);      -- OK(主表存在)
INSERT INTO student(name, class_id) VALUES ('王五', 100);    -- 失败:主表无此 id
INSERT INTO student(name, class_id) VALUES ('赵六', NULL);   -- OK:表示暂未分配班级

6.3 删除/删表规则:被引用的主表记录不能随便删,删表要先删从表

sql 复制代码
DELETE FROM class WHERE name='java03'; -- OK(没人引用)
DELETE FROM class WHERE name='java01'; -- 失败(student 引用了该班级)

并且:如果从表存在外键引用,直接 DROP TABLE class; 会失败;必须先删从表再删主表。

sql 复制代码
DROP TABLE class;   -- 失败:被 student 外键引用
DROP TABLE student; -- 先删从表
DROP TABLE class;   -- 再删主表,成功

7)CHECK:校验约束(限制取值范围/枚举/列间关系)

CHECK 用来限制列或数据能接受的值,保证完整性与准确性。并且有个版本点很关键:MySQL 在 8.0.16 开始全面支持 CHECK,之前版本会忽略 CHECK 定义

7.1 常见:年龄范围 + 性别枚举

sql 复制代码
DROP TABLE IF EXISTS student;
CREATE TABLE student (
  id     BIGINT PRIMARY KEY AUTO_INCREMENT,
  name   VARCHAR(20) NOT NULL,
  age    INT DEFAULT 18,
  gender CHAR(1),
  CHECK (age >= 16),
  CHECK (gender = '男' OR gender = '女')
);

INSERT INTO student(name, age, gender) VALUES ('张三', 17, '男'); -- OK
INSERT INTO student(name, age, gender) VALUES ('张三', 15, '男'); -- 失败:违反 age>=16
INSERT INTO student(name, age, gender) VALUES ('张三', 17, '1');  -- 失败:违反性别枚举

7.2 列与列比较:跨列约束要单独写一行

sql 复制代码
CREATE TABLE t_check (
  c1 INT CHECK (c1 <> 0),
  c2 INT CHECK (c2 > 0),
  c3 INT,
  CHECK (c3 >= c2)
);

INSERT INTO t_check VALUES (-1, 3, 10); -- OK
INSERT INTO t_check VALUES (0, 5, 6);   -- 失败:c1 不能为 0
INSERT INTO t_check VALUES (2, -10, 10);-- 失败:c2 必须 >0
INSERT INTO t_check VALUES (2, 10, 9);  -- 失败:c3 必须 >= c2

8)把这些约束组合起来:一张"能扛事"的 student 表长什么样

来看一份"常用组合":必填字段用 NOT NULL,默认值用 DEFAULT,学号用 UNIQUE,主键用自增 PRIMARY KEY AUTO_INCREMENT,班级关系用 FOREIGN KEY,业务校验用 CHECK

sql 复制代码
CREATE TABLE student (
  id       BIGINT PRIMARY KEY AUTO_INCREMENT,
  name     VARCHAR(20) NOT NULL,
  age      INT DEFAULT 18,
  sno      VARCHAR(10) UNIQUE,
  class_id BIGINT,
  gender   CHAR(1),
  FOREIGN KEY (class_id) REFERENCES class(id),
  CHECK (age >= 16),
  CHECK (gender = '男' OR gender = '女')
);

约束的底层逻辑其实很"工程":让错误更早发生、发生在数据库层、并且以一致方式发生。数据一旦守规矩,上层业务代码就少掉一堆"补锅 if-else",整个系统都会更稳。

相关推荐
__风__2 小时前
PostgreSQL 约束延迟触发
数据库·postgresql
·云扬·3 小时前
InnoDB Cluster 常见管理命令
数据库·mysql
筵陌3 小时前
MySQL内置函数
数据库·mysql
黎明破晓.3 小时前
MySQL基础
数据库·mysql
电商API&Tina3 小时前
【电商API接口】多电商平台数据API接入方案(附带实例)
运维·开发语言·数据库·chrome·爬虫·python·jenkins
秦明月133 小时前
EPLAN电气设计:图层导入与导出操作指南
数据库·经验分享·学习·学习方法·设计规范
奋斗べ青年.4 小时前
【redis】了解redis的主从和集群搭建
数据库·redis·缓存
麦聪聊数据4 小时前
敏感数据安全吗?基于字段级血缘的 PII 数据全链路追踪
数据库·sql·安全
田里的水稻4 小时前
BI_双足机器人舞蹈动作的sim2sim和sim2Real(Gymnasium + Mujoco)
服务器·数据库·机器人