【MySQL基础】一文吃透“表的约束”:从 Null/Default 到主外键的终极安全法则

🔥个人主页:Cx330🌸

❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》

《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔

《Git深度解析》:版本管理实战全解 《Qt 极境架构》MySQL 核心技术与实战

🌟心向往之行必能


🎥Cx330🌸的简介:


目录

前言:

一、什么是表的约束?

[二、基础约束:NULL/NOT NULL 与 DEFAULT](#二、基础约束:NULL/NOT NULL 与 DEFAULT)

[1. 空属性(Null / Not Null)](#1. 空属性(Null / Not Null))

[2. 默认值(Default)](#2. 默认值(Default))

[3. 列描述(Comment)](#3. 列描述(Comment))

[4. 深入剖析 Zerofill](#4. 深入剖析 Zerofill)

[三. 核心约束:主键、自增长与唯一键](#三. 核心约束:主键、自增长与唯一键)

[1.主键(Primary Key)](#1.主键(Primary Key))

[2. 自增长(Auto_Increment)](#2. 自增长(Auto_Increment))

[3. 唯一键(Unique Key)](#3. 唯一键(Unique Key))

[主键 vs 唯一键](#主键 vs 唯一键)

[场景 A:主键的"唯一性"与"非空性"](#场景 A:主键的“唯一性”与“非空性”)

[场景 B:唯一键可以有多个(主键只能有一个)](#场景 B:唯一键可以有多个(主键只能有一个))

[场景 C:唯一键允许为 NULL(空值)](#场景 C:唯一键允许为 NULL(空值))

[💡 核心总结(博主敲黑板)](#💡 核心总结(博主敲黑板))

[四. 关联约束:外键(FOREIGN KEY)](#四. 关联约束:外键(FOREIGN KEY))

[1. 外键(Foreign Key)](#1. 外键(Foreign Key))

深度思考:如何真正理解外键?

[五. 综合案例演练:网上商城数据库设计](#五. 综合案例演练:网上商城数据库设计)

需求

[SQL 脚本实现](#SQL 脚本实现)

结语


前言:

作为C++开发者,我们写代码时最执着的就是类型安全内存安全 以及通过constassert或智能指针来维护类的不变式(Class Invariant)。然而,当我们把目光移向后端存储------数据库时,光靠C++层面的校验是远远不够的。在网络波动、高并发并发写或业务逻辑漏洞面前,数据库才是守护数据的最后防线。

在MySQL中,真正约束字段的是数据类型 ,但数据类型的约束过于单一(比如仅限制整型或字符型)。为了保障业务逻辑的正确性与数据的合法性,我们需要一套更强大的防御机制------表的约束

今天,我们就来一次性吃透MySQL中的核心约束:Null/Not Null、Default、Comment、Zerofill、Primary Key、Auto_Increment、Unique Key 以及 Foreign Key


一、什么是表的约束?

在正式深入具体约束之前,我们首先需要理解"表的约束"究竟是什么,以及为什么需要它。

对于初学者来说,常有一个误区:"既然我可以通过应用层(比如 C++、Java 或 Python 业务代码)来进行数据合法性校验,为什么还需要数据库层面的约束?"

实际上,约束的本质是一种预防机制,是从业务逻辑角度保证数据正确性、完整性和合法性的终极屏障。

  1. 类型约束的局限性 :MySQL 的数据类型本身就是一种最基础的约束。例如,将一个字段定义为 INT ,你就无法插入字符串。然而,类型约束过于单一。例如,一个表示"邮箱"的字段,其类型是 VARCHAR(50),但类型本身无法阻止用户插入两个一模一样的邮箱,也无法阻止插入无意义的空数据。

  2. 多终端与多语言协作的风险:在现代企业级架构中,一个数据库可能同时被 C++ 编写的高并发写入服务、Go 语言编写的微服务、Python 编写的数据分析脚本、甚至 DBA 的手动 SQL 共同读写。如果仅在应用层做校验,任何一方的代码漏洞、规则漏配或手动失误,都会向数据库倾倒脏数据。

  3. 性能与一致性的权衡:把约束规则下沉到数据库引擎层,由 MySQL 在底层原子性地、统一地进行审核,可以极大精简应用层的冗余代码,并从底层物理地掐断产生脏数据的可能。

因此,表的约束不仅是为了限制用户,更是为了保护数据库的引用完整性与实体完整性


二、基础约束:NULL/NOT NULL 与 DEFAULT

1. 空属性(Null / Not Null)

在默认情况下,MySQL 的字段基本都允许为空(NULL)。但在实际企业级开发中,我们尽可能要保证字段不为空(NOT NULL)

为什么不推荐使用 NULL

  1. 无法参与运算NULL代表"无值",它与任何值进行运算都会得到 NULL

  2. 难以索引与统计 :在进行聚合函数计算(如 COUNT)或建立索引时,NULL常会带来意想不到的坑。

案例:班级表设计

从业务逻辑看,班级必须有名字,也必须有上课的教室。如果不做限制,脏数据流入数据库后,业务系统就会崩溃。因此,我们必须在建表时加上not null约束:

复制代码
mysql> create table myclass(
    -> class_name varchar(20) not null,
    -> class_room varchar(10) not null);
Query OK, 0 rows affected (0.02 sec)

如果你尝试插入一条缺少教室信息的数据,MySQL 将直接拒绝,从根本上杜绝了脏数据的产生:

复制代码
mysql> insert into myclass (class_name) values('class1');
ERROR 1364 (HY000): Field 'class_room' doesn't have a default value

2. 默认值(Default)

在业务中,某些数据经常会重复出现(例如:用户的性别默认是"男",年龄默认是"0")。这时我们可以配置 default属性。

复制代码
mysql> create table tt10 (
    -> name varchar(20) not null,
    -> age tinyint unsigned default 0,
    -> sex char(2) default '男'
    -> );
  • 生效机制 :只有在写入数据时省略了该列的赋值,默认值才会生效。

  • 注意NOT NULLDEFAULT不需要同时对一个字段死板地设计。因为一旦设置了 DEFAULT,即便不传值,系统也会用默认值填充,所以该列不可能为空。

3. 列描述(Comment)

对 C++ 程序员来说,Comment就像是类成员变量旁的注释。它没有实际的运行约束含义,专门用来给程序员或 DBA 了解该字段的业务背景。

复制代码
mysql> create table tt12 (
    -> name varchar(20) not null comment '姓名',
    -> age tinyint unsigned default 0 comment '年龄',
    -> sex char(2) default '男' comment '性别'
    -> );
  • 查看方式 :使用 desc tt12;看不到 注释信息的。必须使用 show create table tt12\G 才能查看:

    mysql> show create table tt12\G
    *************************** 1. row ***************************
    Table: tt12
    Create Table: CREATE TABLE tt12 (
    name varchar(20) NOT NULL COMMENT '姓名',
    age tinyint(3) unsigned DEFAULT '0' COMMENT '年龄',
    sex char(2) DEFAULT '男' COMMENT '性别'
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8

4. 深入剖析 Zerofill

新手在看建表语句时,常对 int(10)int(5) 后的括号数字感到困惑。"整型不是占4个字节吗?这里的 5 或 10 是什么意思?"

实际上,如果没有 zerofill 属性,括号内的数字 is meaningless。

Zerofill 的格式化展现

当我们对列添加 zerofill属性后,一旦存储的数值宽度小于设定的宽度(如 int(5)),MySQL 就会在左侧自动填充 0

核心本质:它只是格式化输出!

需要强调的是,这只是显示效果,数据库内部实际存储的依然是原数值 。 我们可以用 C/C++ 程序员最熟悉的 hex() 函数(十六进制转换)来证明:

复制代码
mysql> select a, hex(a) from tt3;
+-------+--------+
| a     | hex(a) |
+-------+--------+
| 00001 | 1      |
+-------+--------+

十六进制输出为 1,充分证明其底层二进制存储并未发生变化。这和 C++ 中的std::setw(5) << std::setfill('0') 格式化输出完全一致!


三. 核心约束:主键、自增长与唯一键

1.主键(Primary Key)

主键是表中最核心的约束,用于唯一约束该字段里面的数据。

特点

  1. 不能重复,不能为空not nullunique)。

  2. 一张表最多只能有一个主键。

  3. 主键所在的列通常是整数类型。

主键的基本操作

  • 创建表时指定

    复制代码
    create table tt13 (
        id int unsigned primary key comment '学号',
        name varchar(20) not null
    );
  • 追加主键 :如果建表时没有设主键,可以通过 alter补上:

    复制代码
    alter table 表名 add primary key(字段名);
  • 删除主键

    复制代码
    alter table 表名 drop primary key;

复合主键

在实际复杂业务中,单一字段无法保证唯一性,此时可以使用复合主键 。例如记录学生的各科成绩:学生ID + 课程代码 共同决定一条唯一的成绩。

复制代码
mysql> create table tt14(
    -> id int unsigned,
    -> course char(10) comment '课程代码',
    -> score tinyint unsigned default 60 comment '成绩',
    -> primary key(id, course) -- id和course合并为复合主键
    -> );

如果强行插入两个 idcourse完全相同的数据,MySQL 就会抛出主键冲突错误:ERROR 1062 (23000): Duplicate entry '1-123' for key 'PRIMARY'。

2. 自增长(Auto_Increment)

自增长通常与主键搭配使用,作为表的逻辑主键 。当不给值时,系统会自动触发自增,在当前已有最大值的基础上 +1

特点

  • 要做自增长的字段,前提是它本身必须是一个索引(Key 一栏有值)。

  • 自增长字段必须是整数

  • 一张表最多只能有一个自增长。

    mysql> create table tt21(
    -> id int unsigned primary key auto_increment,
    -> name varchar(10) not null default ''
    -> );

在连续插入数据后,我们可以通过下述 SQL 语句获取上一次插入的自增 ID:

复制代码
mysql> select last_insert_id();

🔥 补充:什么是索引? 索引是数据库为了快速检索数据而创建的物理存储结构,相当于图书的目录。通过目录中的页码指针,数据库不用全表扫描(O(N)),而是通过特定的查找算法(如 B+ 树,时间复杂度逼近 O(log N))快速定位数据行。

3. 唯一键(Unique Key)

很多人容易搞混主键唯一键。既然我们已经有了主键,为什么还需要唯一键?

唯一键的本质

一张表中往往有多个字段需要唯一性约束(例如:员工的身份证、工号、邮箱、手机号)。但是主键只能有一个

唯一键可以完美解决多字段唯一性约束的问题。同时,唯一键允许为空(NULL),且空字段不做唯一性比较(可以有多个 NULL)

主键 vs 唯一键

在系统设计中,我们通常建议:

  • 主键(Primary Key) :设计为与当前业务无关的递增字段(如自增ID)。这样当业务规则大改时,底层主键完全不需要做任何调整,保持底层结构的稳定性。

  • 唯一键(Unique Key) :用于保障业务层面的无重复约束(如身份证、工号、电子邮箱等)。

    mysql> create table student (
    -> id char(10) unique comment '学号,不能重复,但可以为空',
    -> name varchar(10)
    -> );

假设我们要设计一张 员工表(employees)

1. SQL 建表代码

复制代码
CREATE TABLE employees (
    emp_id INT PRIMARY KEY AUTO_INCREMENT, -- 主键:员工工号(系统内部唯一标识)
    name VARCHAR(50) NOT NULL,             -- 姓名
    id_card CHAR(18) UNIQUE,               -- 唯一键:身份证号
    phone VARCHAR(11) UNIQUE               -- 唯一键:手机号
);

2. 模拟插入数据,看看它们的行为差异:

场景 A:主键的"唯一性"与"非空性"

如果我们插入两个员工:

复制代码
INSERT INTO employees (name, id_card, phone) VALUES ('张三', '110101199003072345', '13800138000');
-- 此时系统自动生成 emp_id = 1

INSERT INTO employees (name, id_card, phone) VALUES ('李四', '110101199205091234', '13900139000');
-- 此时系统自动生成 emp_id = 2
  • 主键(emp_id:由系统自增自动控制(1, 2, 3...),绝对不会重复,也绝对不能为空。它是数据库用来精准定位"这一行是谁"的物理钥匙。
场景 B:唯一键可以有多个(主键只能有一个)

在这张表中,我们同时约束了身份证号(id_card 手机号(phone)都不能重复:

  • 我们不能emp_idid_card都设为主键(因为一张表只能有一个主键)。

  • 但我们可以设置多个唯一键。这样,如果有人试图用同一个手机号注册两个员工,MySQL 就会直接报错拦截。

场景 C:唯一键允许为 NULL(空值)

假设公司招了一个外国专家,他暂时还没有办好本地的身份证 ,或者某个员工入职时没有登记手机号

复制代码
-- 成功插入:身份证和手机号都留空(NULL)
INSERT INTO employees (name, id_card, phone) VALUES ('王五', NULL, NULL);

-- 再次成功插入:另一个员工也暂时没有登记手机号
INSERT INTO employees (name, id_card, phone) VALUES ('赵六', '110101199508085678', NULL);
  • 唯一键的宽容度 :唯一键字段允许写入 NULL

  • 空值不冲突 :即使表里有多个员工的 phone都是 NULL,MySQL 也不会 报"重复冲突"的错误。但如果这是主键,绝对无法写入 NULL

💡 核心总结(博主敲黑板)

特性 主键 (Primary Key) 唯一键 (Unique Key)
数量限制 一张表只能有一个 一张表可以有多个
是否允许为空 绝对不能NULL 允许NULL
设计定位 系统级定位:与业务无关(如自增ID),用于做表连接和索引优化。 业务级防重:保障真实业务数据的唯一性(如手机号、身份证、邮箱)。

四. 关联约束:外键(FOREIGN KEY)

1. 外键(Foreign Key)

在关系型数据库中,表与表之间绝对不是孤立存在的。外键用于定义主表(Parent Table)从表(Child Table)之间的关联。

语法

复制代码
foreign key (从表列名) references 主表名 (主表主键/唯一键)

案例设计

我们设计一个学生表(从表)和班级表(主表)的关系:

  • 主表:班级表

    create table myclass (
    id int primary key,
    name varchar(30) not null comment '班级名'
    );

  • 从表:学生表(引入外键)

    create table stu (
    id int primary key,
    name varchar(30) not null comment '学生名',
    class_id int,
    foreign key (class_id) references myclass (id) -- 关联主表
    );

深度思考:如何真正理解外键?

有些同学可能会问:"如果不创建外键约束,我们只要在代码层面自己保证关联不就行了吗?"

这在理论上可行,但在高并发、多团队协作的项目中,这几乎是不可能完成的任务。 比如:学校目前只有 101 和 102 两个班。如果一个新同学入学时被录入了不存在的 103 班,而在应用层代码中漏掉了这个逻辑校验,那么脏数据就会污染数据库。

建立外键的本质,就是把这种"业务关联的审核工作"直接交下了底层的 MySQL。 提前告诉 MySQL 表之间的硬性关联,一旦用户尝试插入不符合业务逻辑的数据,MySQL 就会在最底层直接将该操作枪毙!


五. 综合案例演练:网上商城数据库设计

最后,我们用一个完整的网上商城库表设计,把上面学到的约束融会贯通:

需求

  1. 包含三个表:商品表(goods)客户表(customer)购买订单表(purchase)

  2. 客户的姓名不能为空,邮箱必须唯一。

  3. 客户的性别限制为 '男' 或 '女'。

  4. 正确建立主外键关系。

SQL 脚本实现

复制代码
-- 1. 创建数据库
create database if not exists bit32mall
default character set utf8;

use bit32mall;

-- 2. 创建商品表 (goods)
create table if not exists goods
(
    goods_id int primary key auto_increment comment '商品编号',
    goods_name varchar(32) not null comment '商品名称',
    unitprice int not null default 0 comment '单价, 单位分', -- 避免浮点数精度丢失, 采用分作为单位
    category varchar(12) comment '商品分类',
    provider varchar(64) not null comment '供应商名称'
);

-- 3. 创建客户表 (customer)
create table if not exists customer
(
    customer_id int primary key auto_increment comment '客户编号',
    name varchar(32) not null comment '客户姓名',
    address varchar(256) comment '客户地址',
    email varchar(64) unique key comment '电子邮箱',
    sex enum('男', '女') not null comment '性别',
    card_id char(18) unique key comment '身份证'
);

-- 4. 创建购买订单表 (purchase)
create table if not exists purchase
(
    order_id int primary key auto_increment comment '订单号',
    customer_id int comment '客户编号',
    goods_id int comment '商品编号',
    nums int default 0 comment '购买数量',
    -- 建立外键约束,保护引用完整性
    foreign key (customer_id) references customer (customer_id),
    foreign key (goods_id) references goods (goods_id)
);

结语

在 C++ 的底层世界,我们通过异常、RAII 和严谨的指针逻辑保障程序的稳健运行;而在数据库的世界,约束就是我们最强大的 RAII 守护盾牌

一个结构设计良好、约束严密的数据库表,不仅能让后端的业务代码写起来极度清爽、优雅,更能在各种极端高并发的写入场景下保持坚不可摧的持久化完整性。

如果你觉得这篇内容对你有帮助,不妨点赞、收藏,我们下期见!

相关推荐
醉颜凉2 小时前
“三把钥匙开一把锁”:多签钱包深度解析与资产安全提升指南
安全·区块链
Haoxuekeji2 小时前
山东 AI 智能批改校园电子阅卷企业
大数据·人工智能·深度学习·安全·ai
程序员JerrySUN2 小时前
Jetson 刷机深度解析:flash.sh vs l4t_initrd_flash.sh(含安全与磁盘加密对比)
linux·网络·arm开发·安全·系统安全
c238562 小时前
第二篇:《测试指挥官:可视化单题自测框架(含 assert 实操)》
java·数据库·c++·算法·安全性测试
大阳光男孩2 小时前
Spring Boot 整合 Debezium 实现 MySQL 增量数据监听(嵌入式版)
spring boot·后端·mysql
我科绝伦(Huanhuan Zhou)2 小时前
MySQL极端场景数据丢失防护:从Crash-Safe到异地备份
数据库·mysql
辞旧 lekkk2 小时前
【Redis初阶】常见数据类型
开发语言·数据库·c++·redis·学习·缓存·bootstrap
aduzhe3 小时前
关于lib文件格式(keil/unix)
服务器·unix
银河麒麟操作系统3 小时前
闸机高效稳跑,通勤提速升级!银河麒麟赋能北京轨交新基建
运维·服务器·安全·交通物流