2.11 约束的使用(主键、外键、非空、唯一、默认值约束)

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);

分步操作

  1. 执行建表语句,不指定主键。

  2. 使用ALTER TABLE添加主键。

  3. 尝试插入重复的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);

分步操作

  1. 先创建users表(父表),再创建orders表(子表)。

  2. 插入用户数据:INSERT INTO users VALUES (1, '张三')

  3. 插入订单数据,user_id使用存在的1:成功。

  4. 尝试插入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;

分步操作

  1. 创建表时某字段不加NOT NULL

  2. 插入一条数据,该字段留空(NULL),成功。

  3. 执行ALTER TABLE添加NOT NULL约束。

  4. 再次尝试插入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);

分步操作

  1. 插入两条相同手机号的记录,第二条报错。

  2. 插入两条手机号为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;

分步操作

  1. 插入订单数据时只填order_id,不填order_statuscreate_time

  2. 查询发现这两个字段自动填充了默认值。

避坑提醒

  • 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;

分步操作

  1. 创建一个带有各种约束的表。

  2. 执行删除约束语句,验证约束不再生效。

  3. 注意删除主键前,如果有外键依赖,需先删除外键。

实操避坑提醒 :删除主键时,如果该主键被其他表作为外键引用,会报错。必须先删除子表的外键,或者使用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字段)。

  • 手机号唯一但不等于可以明文存储:必须加密,且只有在必要业务场景(如发送短信)时解密。

  • 默认值设计:状态字段的默认值要符合业务预期,比如订单状态默认"待支付",而不是"已支付"。

结语

约束是数据库的"执法者",它强制数据遵守规则,让你不必在应用层写大量校验代码。掌握了主键、外键、非空、唯一、默认值这五大约束,你就能设计出规范、可靠的电商数据表。

有问题的评论区留言,我看到会回复。

相关推荐
城数派4 小时前
2025年南京市全类别POI(55W+数据)
数据库·arcgis·信息可视化·数据分析·excel
Fᴏʀ ʏ꯭ᴏ꯭ᴜ꯭.4 小时前
MySQL半同步复制与GTID实战详解
android·mysql·adb
大大大大晴天️4 小时前
Flink技术实践-Flink SQL 开发中的隐蔽陷阱
大数据·sql·flink
SPC的存折5 小时前
openEuler 24.03 MariaDB Galera 集群部署指南(cz)
linux·运维·服务器·数据库·mysql
仲芒5 小时前
[24年单独笔记] MySQL 常用的 DML 命令
数据库·笔记·mysql
SPC的存折5 小时前
MySQL 8.0 分库分表
linux·运维·服务器·数据库·mysql
翻斗包菜7 小时前
第 03 章 Python 操作 MySQL 数据库实战全解
数据库·python·mysql
SPC的存折7 小时前
1、MySQL故障排查与运维案例
linux·运维·服务器·数据库·mysql
悟空码字7 小时前
MySQL性能优化的天花板:10条你必须掌握的顶级SQL分析技巧
java·后端·mysql