2.11 约束的使用(主键、外键、非空、唯一、默认值约束)
开篇:为什么数据总是乱?因为你没给表立规矩
我刚入行时,接手了一张"用户信息表",里面同一个用户出现了三次,手机号有的写13812345678,有的写138****5678,还有一行用户名为空。我问运营怎么回事,他说"录入的时候没注意"。后来我在这张表上做用户画像分析,统计出来的结果完全是错的------一个用户被算了三次,重复率离谱。
从那以后我明白了:数据表必须要有"规矩"。这些规矩在SQL里就叫"约束"(Constraint)。约束是强制数据必须遵守的规则,比如"订单号不能重复""用户ID不能为空""金额不能小于0"。没有约束的表,就像没有交通规则的路,数据早晚会乱成一锅粥。
这一章我会带你彻底搞懂SQL中的五大约束:主键、外键、非空、唯一、默认值。学完之后,你能设计出规范、可靠的电商业务表,从源头杜绝脏数据。
学习前准备:
-
已完成MySQL安装(参考之前的教程)
-
已安装DBeaver或Navicat可视化工具
-
准备一个练习数据库(比如
constraint_demo)
SQL约束基础认知
什么是约束
约束是数据库自动执行的数据校验规则。当你尝试插入或修改数据时,数据库会检查是否违反约束。如果违反,操作会被拒绝并报错。
五大约束概览
| 约束类型 | 作用 | 电商典型字段 |
|---|---|---|
| 主键约束 | 唯一标识一行,不能重复不能为空 | 订单号、用户ID |
| 外键约束 | 关联另一张表,保证引用完整性 | 订单表的用户ID → 用户表的用户ID |
| 非空约束 | 字段不能为空 | 订单金额、下单时间 |
| 唯一约束 | 字段值不能重复(可空) | 手机号、身份证号 |
| 默认值约束 | 不填时自动填入默认值 | 订单状态默认"待支付" |
为什么数据分析师必须懂约束
-
保证分析结果准确:没有重复订单,没有空值,统计才可信。
-
避免踩坑:不了解外键约束,可能因为关联不上数据而漏统计。
-
设计测试表:在本地练习时,你会自己建表,需要约束来模拟真实场景。
我的踩坑经历 :有一次我统计"每个用户的订单数",直接GROUP BY user_id,结果有个user_id为空的订单被归到了一组,导致统计多了一条"空用户"的订单。后来检查发现,订单表没有加NOT NULL约束,源头数据就缺了用户ID。如果建表时加了非空约束,这种脏数据根本进不来。
学习前准备:环境搭建(简述)
如果你已经完成了之前的MySQL安装和DBeaver配置,可以直接跳过本节。否则按以下步骤快速搭建。
步骤1:安装MySQL(Windows/Mac),设置root密码。
步骤2 :安装DBeaver社区版,连接到本地MySQL(localhost:3306)。
步骤3:创建练习数据库。
SQL
CREATE DATABASE constraint_demo
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE constraint_demo;
五大约束详解
主键约束(PRIMARY KEY)
核心定义
主键用于唯一标识表中的每一行。每个表必须有且只能有一个主键。主键字段不能重复,也不能为空。
电商场景实操
订单表用订单号做主键,用户表用用户ID做主键。
建表时添加主键(单列):
SQL
CREATE TABLE users (
user_id INT PRIMARY KEY COMMENT '用户ID',
user_name VARCHAR(50) NOT NULL
);
建表时添加主键(多列联合主键):
订单明细表用(order_id, product_id)组合做主键。
SQL
CREATE TABLE order_items (
order_id VARCHAR(50),
product_id INT,
quantity INT,
PRIMARY KEY (order_id, product_id)
);
表创建后添加主键(假设建表时忘了):
SQL
ALTER TABLE users ADD PRIMARY KEY (user_id);
分步操作:
-
执行建表语句,不指定主键。
-
使用
ALTER TABLE添加主键。 -
尝试插入重复的
user_id,验证报错。
SQL
INSERT INTO users (user_id, user_name) VALUES (1, '张三'); -- 成功
INSERT INTO users (user_id, user_name) VALUES (1, '李四'); -- 报错:Duplicate entry
避坑提醒
-
不要用业务字段(如手机号)做主键,因为手机号可能会换,而且可能为空。
-
推荐使用自增整数或雪花ID作为主键。
-
联合主键的列顺序有讲究,最常查询的列放前面。
我的踩坑经历 :早期我设计商品表时用product_code做主键,后来公司调整编码规则,所有商品编码都要改。我不得不先删除外键依赖,再改主键,折腾了一整天。从此以后,所有表都用无业务含义的自增ID做主键。
外键约束(FOREIGN KEY)
核心定义
外键用于建立两张表之间的关联,保证数据的一致性。外键列的值必须在被引用表的主键中存在(或为空)。
电商场景实操
订单表中的user_id引用用户表中的user_id,保证每个订单都属于一个存在的用户。
建表时添加外键:
SQL
CREATE TABLE orders (
order_id VARCHAR(50) PRIMARY KEY,
user_id INT,
amount DECIMAL(10,2),
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
表创建后添加外键:
SQL
ALTER TABLE orders ADD CONSTRAINT fk_orders_users
FOREIGN KEY (user_id) REFERENCES users(user_id);
分步操作:
-
先创建
users表(父表),再创建orders表(子表)。 -
插入用户数据:
INSERT INTO users VALUES (1, '张三')。 -
插入订单数据,
user_id使用存在的1:成功。 -
尝试插入
user_id = 999的订单:报错,因为用户表没有999。
级联操作
-
ON DELETE CASCADE:删除用户时,自动删除其订单。 -
ON DELETE SET NULL:删除用户时,将其订单的user_id设为NULL。
SQL
CREATE TABLE orders (
order_id VARCHAR(50) PRIMARY KEY,
user_id INT,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
);
避坑提醒
-
外键会降低插入、删除性能,在数据仓库或分析库中有时会省略物理外键,但逻辑上仍要保持一致。
-
关联的字段数据类型必须完全一致(比如都是
INT)。 -
删除父表记录前,必须先处理子表的外键引用,否则会报错。
我的踩坑经历 :有一次我想删除一个不再使用的用户,执行DELETE FROM users WHERE user_id = 123,结果报错"外键约束失败"。我忘了这个用户还有订单记录。后来加了ON DELETE CASCADE,再删用户时订单自动被清掉,省了不少事。
非空约束(NOT NULL)
核心定义
非空约束强制字段必须有值 ,不能为NULL。
电商场景实操
订单金额、下单时间、订单号都不能为空。
建表时添加:
SQL
CREATE TABLE orders (
order_id VARCHAR(50) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
create_time DATETIME NOT NULL
);
表创建后添加:
SQL
ALTER TABLE orders MODIFY amount DECIMAL(10,2) NOT NULL;
分步操作:
-
创建表时某字段不加
NOT NULL。 -
插入一条数据,该字段留空(
NULL),成功。 -
执行
ALTER TABLE添加NOT NULL约束。 -
再次尝试插入NULL值,报错。
避坑提醒
-
如果表中已有NULL值,添加
NOT NULL约束会失败,需要先更新NULL值为默认值。 -
不是所有字段都需要
NOT NULL,比如"支付时间"在未支付时可以空。
唯一约束(UNIQUE)
核心定义
唯一约束保证字段值在整个表中不重复,但允许有多个NULL值(MySQL中NULL视为不同)。
电商场景实操
用户手机号、身份证号应该唯一;优惠券码不能重复。
建表时添加:
SQL
CREATE TABLE users (
user_id INT PRIMARY KEY,
phone CHAR(11) UNIQUE COMMENT '手机号唯一'
);
表创建后添加:
SQL
ALTER TABLE users ADD CONSTRAINT unique_phone UNIQUE (phone);
分步操作:
-
插入两条相同手机号的记录,第二条报错。
-
插入两条手机号为NULL的记录,都成功(NULL不参与唯一比较)。
避坑提醒
-
唯一约束和主键的区别:一个表只能有一个主键,但可以有多个唯一约束。
-
如果需要联合唯一(比如同一个用户不能对同一商品重复评价),可以用
UNIQUE(user_id, product_id)。
默认值约束(DEFAULT)
核心定义
默认值约束指定当插入时未提供该列的值,自动填入预设的默认值。
电商场景实操
订单状态默认为"待支付";创建时间默认为当前时间。
建表时添加:
SQL
CREATE TABLE orders (
order_id VARCHAR(50) PRIMARY KEY,
order_status TINYINT DEFAULT 1 COMMENT '1待支付',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
表创建后添加:
SQL
ALTER TABLE orders ALTER COLUMN order_status SET DEFAULT 1;
分步操作:
-
插入订单数据时只填
order_id,不填order_status和create_time。 -
查询发现这两个字段自动填充了默认值。
避坑提醒
-
DEFAULT只在插入时生效,UPDATE时不会自动恢复默认值。 -
CURRENT_TIMESTAMP可以作为DATETIME字段的默认值,但需要MySQL 5.6以上版本。
表创建时添加约束与表创建后修改/删除约束
创建表时一次性定义所有约束
SQL
CREATE TABLE orders (
order_id VARCHAR(50) PRIMARY KEY,
user_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
order_status TINYINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_orders_users FOREIGN KEY (user_id) REFERENCES users(user_id)
);
表创建后添加约束
SQL
-- 添加主键
ALTER TABLE products ADD PRIMARY KEY (product_id);
-- 添加外键
ALTER TABLE order_items ADD CONSTRAINT fk_items_orders FOREIGN KEY (order_id) REFERENCES orders(order_id);
-- 添加唯一约束
ALTER TABLE users ADD CONSTRAINT unique_email UNIQUE (email);
-- 添加非空约束(通过MODIFY)
ALTER TABLE products MODIFY price DECIMAL(10,2) NOT NULL;
-- 设置默认值
ALTER TABLE products ALTER COLUMN status SET DEFAULT 1;
删除约束
SQL
-- 删除主键
ALTER TABLE products DROP PRIMARY KEY;
-- 删除外键(需要知道约束名)
ALTER TABLE order_items DROP FOREIGN KEY fk_items_orders;
-- 删除唯一约束
ALTER TABLE users DROP INDEX unique_email;
-- 删除非空约束(通过MODIFY去掉NOT NULL)
ALTER TABLE products MODIFY price DECIMAL(10,2);
-- 删除默认值
ALTER TABLE products ALTER COLUMN status DROP DEFAULT;
分步操作:
-
创建一个带有各种约束的表。
-
执行删除约束语句,验证约束不再生效。
-
注意删除主键前,如果有外键依赖,需先删除外键。
实操避坑提醒 :删除主键时,如果该主键被其他表作为外键引用,会报错。必须先删除子表的外键,或者使用SET FOREIGN_KEY_CHECKS=0临时禁用(不推荐生产环境)。
电商多业务表关联场景下约束的组合使用
场景:用户-订单-订单明细三表约束设计
用户表 :主键user_id,手机号唯一,用户名非空。
SQL
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(50) NOT NULL,
phone CHAR(11) UNIQUE,
register_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
订单表 :主键order_id,外键user_id引用用户表,金额非空,状态默认1。
SQL
CREATE TABLE orders (
order_id VARCHAR(50) PRIMARY KEY,
user_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
order_status TINYINT NOT NULL DEFAULT 1,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
订单明细表 :联合主键(order_id, product_id),外键order_id引用订单表。
SQL
CREATE TABLE order_items (
order_id VARCHAR(50),
product_id INT,
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (order_id) REFERENCES orders(order_id)
);
约束如何保证数据完整性
-
用户表:手机号唯一,防止重复注册。
-
订单表 :
user_id必须存在,防止孤儿订单。 -
订单明细 :
order_id必须存在,防止明细无主订单;联合主键防止同一订单重复添加同一商品。
测试约束效果
SQL
-- 插入用户
INSERT INTO users (user_name, phone) VALUES ('张三', '13800000001');
-- 插入订单(正确)
INSERT INTO orders (order_id, user_id, amount) VALUES ('ORD001', 1, 299.00);
-- 尝试插入不存在的用户(失败)
INSERT INTO orders (order_id, user_id, amount) VALUES ('ORD002', 999, 199.00); -- 外键约束失败
-- 尝试插入重复主键的订单明细(失败)
INSERT INTO order_items VALUES ('ORD001', 101, 2, 99.00); -- 成功
INSERT INTO order_items VALUES ('ORD001', 101, 1, 99.00); -- 主键冲突
综合实操案例:服饰类目电商店铺核心业务表的约束创建
案例背景
为一家服饰类目天猫店铺设计核心业务表:用户表、商品表、订单表、订单详情表。要求利用五大约束确保数据规范。
分步操作
步骤1:创建数据库
SQL
CREATE DATABASE fashion_shop
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE fashion_shop;
步骤2:创建用户表
SQL
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
user_name VARCHAR(50) NOT NULL COMMENT '用户名',
phone CHAR(11) UNIQUE COMMENT '手机号',
register_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
user_level TINYINT NOT NULL DEFAULT 1 COMMENT '等级:1普通,2银卡,3金卡'
);
步骤3:创建商品表
SQL
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '商品ID',
product_name VARCHAR(200) NOT NULL COMMENT '商品名称',
category VARCHAR(50) NOT NULL COMMENT '类目',
price DECIMAL(10,2) NOT NULL COMMENT '价格',
stock INT NOT NULL DEFAULT 0 COMMENT '库存',
status TINYINT NOT NULL DEFAULT 1 COMMENT '1上架,2下架'
);
步骤4:创建订单表
SQL
CREATE TABLE orders (
order_id VARCHAR(50) PRIMARY KEY COMMENT '订单号',
user_id INT NOT NULL COMMENT '用户ID',
order_amount DECIMAL(10,2) NOT NULL COMMENT '订单总金额',
order_status TINYINT NOT NULL DEFAULT 1 COMMENT '1待支付,2已支付,3已取消,4已完成',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间',
pay_time DATETIME COMMENT '支付时间',
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE RESTRICT
);
步骤5:创建订单详情表
SQL
CREATE TABLE order_details (
detail_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '详情ID',
order_id VARCHAR(50) NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL COMMENT '数量',
price DECIMAL(10,2) NOT NULL COMMENT '单价',
FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE RESTRICT,
UNIQUE KEY uk_order_product (order_id, product_id) -- 联合唯一,防止重复商品
);
步骤6:验证约束效果
SQL
-- 插入测试数据
INSERT INTO users (user_name, phone) VALUES ('张小美', '13912345678');
INSERT INTO products (product_name, category, price, stock) VALUES ('碎花连衣裙', '女装', 299.00, 100);
INSERT INTO orders (order_id, user_id, order_amount) VALUES ('ORD001', 1, 299.00);
INSERT INTO order_details (order_id, product_id, quantity, price) VALUES ('ORD001', 1, 1, 299.00);
-- 尝试违反约束
INSERT INTO order_details (order_id, product_id, quantity, price) VALUES ('ORD001', 1, 1, 299.00); -- 联合唯一冲突
INSERT INTO orders (order_id, user_id, order_amount) VALUES ('ORD002', 999, 199.00); -- 外键失败(用户不存在)
结果验证
-
所有表创建成功,约束生效。
-
插入重复订单明细时被拒绝。
-
插入不存在的用户订单被拒绝。
-
删除用户时,如果该用户有订单且外键
ON DELETE RESTRICT,会阻止删除(保护数据)。
📌 电商数据合规提示 :用户表中的phone字段虽然是唯一的,但属于个人敏感信息。在实际生产环境中,建议对phone字段加密存储,或使用脱敏视图对外提供。另外,外键约束虽然保证了数据一致性,但也要注意不要过度依赖物理外键导致性能下降,大数据量下可改为逻辑外键。
本章踩坑清单与合规总结
新手常见踩坑
| 错误 | 原因 | 正确做法 |
|---|---|---|
| 建表时忘记主键 | 表无唯一标识 | 每张表都要有主键 |
| 外键字段类型不一致 | user_id在用户表是INT,在订单表是VARCHAR |
保持一致类型 |
| 添加NOT NULL时表中已有NULL | 报错 | 先更新NULL值为默认值 |
| 唯一约束允许NULL | 想强制必须有值 | 加上NOT NULL |
| 删除被引用的主键记录 | 外键约束阻止 | 先删除子表记录或使用CASCADE |
电商数据合规红线
-
外键与数据删除 :
ON DELETE CASCADE要慎用,避免误删用户导致订单也被删,审计时无法追溯。通常电商系统会采用软删除(增加is_deleted字段)。 -
手机号唯一但不等于可以明文存储:必须加密,且只有在必要业务场景(如发送短信)时解密。
-
默认值设计:状态字段的默认值要符合业务预期,比如订单状态默认"待支付",而不是"已支付"。
结语
约束是数据库的"执法者",它强制数据遵守规则,让你不必在应用层写大量校验代码。掌握了主键、外键、非空、唯一、默认值这五大约束,你就能设计出规范、可靠的电商数据表。
有问题的评论区留言,我看到会回复。