约束类型对比
表格中横向对比了主键、唯一、非空、外键四种约束的核心区别:
| 约束类型 | 唯一性 | 非空性 | 数量限制 | 作用范围 | 备注 |
|---|---|---|---|---|---|
| 主键约束 (Primary Key) | 必须唯一 | 不能为 NULL | 每表仅一个 | 单列或多列组合 | 标识记录的唯一性,通常自增 |
| 唯一约束 (Unique Key) | 必须唯一 | 允许 NULL | 每表可多个 | 单列或多列组合 | 保证列值不重复,NULL 值可重复出现 |
| 非空约束 (Not Null) | 不要求唯一 | 不能为 NULL | 每列可独立设置 | 单列 | 仅限制 NULL 值,不限制重复值 |
| 外键约束 (Foreign Key) | 允许重复 | 允许 NULL | 每表可多个 | 单列或多列组合 | 引用另一表的主键或唯一键,保证参照完整性 |
主键约束 (Primary Key)
每一个表中只能有一个列被设置为主键约束,主键约束通常是用来标记表中数据的唯一性。主键约束要求当前的列不能为 NULL 值,主键约束要求当前的列值是唯一存在的,不能重复。
mysql
-- 添加主键约束
ALTER TABLE 表名 ADD CONSTRAINT 约束名字 PRIMARY KEY (列);
-- 添加好主键约束后,可以通过如下语句查看
DESC 表名; -- description 描述
SHOW KEYS FROM 表名;
-- 发现自己定义的主键名字没有用上,可以简写为
ALTER TABLE 表名 ADD PRIMARY KEY (id);
-- 添加主键之后若想要让主键自增,可以做相应的设计
ALTER TABLE myclass MODIFY classid INT(4) AUTO_INCREMENT;
ALTER TABLE myclass CHANGE classid classid INT(4) AUTO_INCREMENT;
-- 没有做起始值的说明,主键列的值会从 1 开始
ALTER TABLE myclass AUTO_INCREMENT = 10;
-- 删除主键约束
ALTER TABLE myclass DROP PRIMARY KEY;
-- 注意:删除主键约束以后,不重复的特性取消了,非空特性还在
ALTER TABLE myclass MODIFY classid INT(4) NULL;
实战场景:用户订单表,订单号需唯一且非空,作为主键自增
mysql
-- 1. 创建订单表,并设置 order_id 为主键且自增
CREATE TABLE orders (
order_id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
order_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (order_id)
);
-- 2. 插入数据,主键 order_id 会自动生成
INSERT INTO orders (user_id, amount) VALUES (1001, 299.99);
INSERT INTO orders (user_id, amount) VALUES (1002, 150.50);
-- 3. 验证主键约束:尝试插入重复的 order_id 会失败
-- INSERT INTO orders (order_id, user_id, amount) VALUES (1, 1003, 88.88); -- 错误:Duplicate entry '1' for key 'PRIMARY'
-- 4. 验证非空约束:尝试插入 NULL 到 order_id 会失败
-- INSERT INTO orders (order_id, user_id, amount) VALUES (NULL, 1004, 200.00); -- 错误:Column 'order_id' cannot be null
-- 5. 查看表结构和主键信息
DESC orders;
SHOW KEYS FROM orders;
唯一约束 (Unique Key)
可以为表中的某一个列添加唯一约束,约束与主键类似。唯一约束表示的是列的值不能重复,可以为空,唯一约束在表中可以存在多个列。
mysql
-- 添加唯一约束
ALTER TABLE 表名 ADD CONSTRAINT 约束名 UNIQUE [KEY] (列);
ALTER TABLE myclass ADD CONSTRAINT uk_myclass UNIQUE [KEY] (loc);
-- 可以简写为如下效果
ALTER TABLE myclass ADD UNIQUE KEY (loc); -- 约束名默认的列名
-- 删除唯一约束
ALTER TABLE myclass DROP INDEX 约束名;
实战场景:用户注册表,邮箱需唯一但可为空(允许用户暂不绑定邮箱)
mysql
-- 1. 创建用户表,为 email 列添加唯一约束
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE, -- 唯一约束,允许 NULL
phone VARCHAR(20)
);
-- 2. 插入数据,email 可以为 NULL,但不能重复
INSERT INTO users (username, email, phone) VALUES ('张三', 'zhangsan@example.com', '13800138000');
INSERT INTO users (username, email, phone) VALUES ('李四', NULL, '13900139000'); -- 允许 email 为 NULL
INSERT INTO users (username, email, phone) VALUES ('王五', 'wangwu@example.com', '13600136000');
-- 3. 验证唯一约束:尝试插入重复的 email 会失败
-- INSERT INTO users (username, email, phone) VALUES ('赵六', 'zhangsan@example.com', '13500135000'); -- 错误:Duplicate entry 'zhangsan@example.com' for key 'email'
-- 4. 验证 NULL 值可以重复:可以再次插入 email 为 NULL 的记录
INSERT INTO users (username, email, phone) VALUES ('钱七', NULL, '13700137000'); -- 成功,因为 NULL 不等于 NULL
-- 5. 查看唯一约束
SHOW KEYS FROM users;
非空约束 (Not Null)
在表中的某一个列上添加非空约束。当前列的值不能为 NULL。
mysql
ALTER TABLE 表名 MODIFY 原列名 原类型 原长度 [NOT] NULL DEFAULT xxx;
ALTER TABLE 表名 CHANGE 原列名 原列名 原类型 原长度 [NOT] NULL DEFAULT xxx;
实战场景:员工信息表,员工姓名和入职日期不能为空
mysql
-- 1. 创建员工表,设置 name 和 hire_date 为非空
CREATE TABLE employees (
emp_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL, -- 非空约束
department VARCHAR(50),
hire_date DATE NOT NULL, -- 非空约束
salary DECIMAL(10, 2)
);
-- 2. 插入有效数据
INSERT INTO employees (name, department, hire_date, salary) VALUES ('张三', '技术部', '2023-01-15', 15000.00);
INSERT INTO employees (name, department, hire_date, salary) VALUES ('李四', '市场部', '2023-03-20', 12000.00);
-- 3. 验证非空约束:尝试插入 NULL 到 name 或 hire_date 会失败
-- INSERT INTO employees (name, department, hire_date, salary) VALUES (NULL, '财务部', '2023-05-10', 13000.00); -- 错误:Column 'name' cannot be null
-- INSERT INTO employees (name, department, hire_date, salary) VALUES ('王五', '人事部', NULL, 11000.00); -- 错误:Column 'hire_date' cannot be null
-- 4. 使用默认值:可以为非空列设置默认值
ALTER TABLE employees MODIFY department VARCHAR(50) NOT NULL DEFAULT '未分配';
-- 5. 插入数据时,如果不指定 department,将使用默认值
INSERT INTO employees (name, hire_date, salary) VALUES ('赵六', '2023-07-01', 14000.00); -- department 自动为 '未分配'
-- 6. 查看表结构,确认非空约束
DESC employees;
外键约束 (Foreign Key)
表中可以有多个列被设置为外键约束,当前列的值可以为空,可以重复。当前列的值不能随便地填写,值去另外一张表内寻找。外键是当前列的值受到另外一张表某一个列的影响,另外一张表的列是唯一约束(主键、唯一),谨慎使用。
mysql
-- 添加外键约束,不建议简写
ALTER TABLE 表名字 ADD CONSTRAINT fk_当前表_关联表 FOREIGN KEY (列) REFERENCES 另一个表 (列);
-- 删除外键约束
ALTER TABLE 表名字 DROP FOREIGN KEY 约束名字;
-- 注意:通过上述语句其实已经将外键约束删掉了
-- 自动在当前表内添加一个新的 KEY
-- 需要再次手动将这个生成的 KEY 删掉,外键约束才真的删除干净
ALTER TABLE 表名字 DROP KEY 约束名字;
-- 查看
DESC 表;
SHOW KEYS FROM 表;
SHOW CREATE TABLE 表名;
实战场景:订单详情表,关联订单表,确保每个详情都属于一个已存在的订单
mysql
-- 1. 创建主表(订单表)
CREATE TABLE orders_main (
order_id INT PRIMARY KEY AUTO_INCREMENT,
order_number VARCHAR(20) UNIQUE NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL
);
-- 2. 创建从表(订单详情表),并添加外键约束
CREATE TABLE order_details (
detail_id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT, -- 外键列,引用 orders_main.order_id
product_name VARCHAR(100) NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10, 2) NOT NULL,
CONSTRAINT fk_order_details_orders FOREIGN KEY (order_id) REFERENCES orders_main(order_id)
);
-- 3. 在主表中插入数据
INSERT INTO orders_main (order_number, total_amount) VALUES ('ORD20230528001', 500.00);
INSERT INTO orders_main (order_number, total_amount) VALUES ('ORD20230528002', 750.00);
-- 4. 在从表中插入有效数据(order_id 存在)
INSERT INTO order_details (order_id, product_name, quantity, price) VALUES (1, '笔记本电脑', 1, 500.00);
INSERT INTO order_details (order_id, product_name, quantity, price) VALUES (2, '鼠标', 2, 50.00);
-- 5. 验证外键约束:尝试插入不存在的 order_id 会失败
-- INSERT INTO order_details (order_id, product_name, quantity, price) VALUES (999, '键盘', 1, 100.00); -- 错误:Cannot add or update a child row: a foreign key constraint fails
-- 6. 验证外键约束:尝试删除主表中被引用的记录会失败
-- DELETE FROM orders_main WHERE order_id = 1; -- 错误:Cannot delete or update a parent row: a foreign key constraint fails
-- 7. 查看外键约束信息
SHOW CREATE TABLE order_details;
约束选择实战建议总结
在实际数据库设计时,应根据业务需求选择合适的约束类型,甚至组合使用。表格中总结了四种约束的典型应用场景和组合最佳实践:
| 约束类型 | 优先选择场景 | 组合使用建议 |
|---|---|---|
| 主键约束 | 1. 实体表的主标识列 (如用户ID、订单ID) 2. 需要唯一且非空 的业务关键字段 3. 需要建立聚集索引提高查询性能的表 | • 主键 + 自增 :适用于代理主键,如 id INT AUTO_INCREMENT PRIMARY KEY • 复合主键 :多列联合唯一标识,如订单明细表(订单ID+商品ID) • 主键 + 唯一约束:主键保证唯一标识,其他业务字段(如身份证号)用唯一约束保证业务唯一性 |
| 唯一约束 | 1. 业务上要求唯一但允许为空 的字段(如邮箱、手机号) 2. 已有主键,但其他字段也需要唯一性保证 (如用户名、工号) 3. 防止数据重复录入的关键业务字段 | • 唯一 + 非空 :当字段必须唯一且必填时,如 username VARCHAR(50) UNIQUE NOT NULL • 多列联合唯一 :确保几列组合值不重复,如 UNIQUE KEY uk_user_email (user_id, email) • 唯一约束作为外键引用目标:当被其他表的外键引用时,该列需有唯一约束 |
| 非空约束 | 1. 业务必填字段 (如姓名、创建时间、金额) 2. 需要参与计算或比较 的字段,避免NULL值干扰 3. 数据质量要求高的核心字段 | • 非空 + 默认值 :为字段设置合理默认值,如 status VARCHAR(10) NOT NULL DEFAULT 'active' • 主键/唯一约束 + 非空 :主键和唯一约束本身包含非空特性,无需重复声明 • 外键 + 非空 :当关联关系必须存在时,如 order_id INT NOT NULL |
| 外键约束 | 1. 强关联关系 的表间数据完整性(如订单-订单详情) 2. 需要级联操作 (删除/更新)的场景 3. 数据一致性要求高的规范化数据库设计 | • 外键 + 非空 :当子表记录必须关联父表时 • 外键 + 索引 :外键列自动创建索引,但复合外键需手动优化 • 谨慎使用级联 :ON DELETE CASCADE 可能引发数据误删,需评估业务风险 • 高并发场景慎用:外键可能影响插入/更新性能,可考虑应用层保证一致性 |
综合设计原则
-
主键是基石:每张表都应有一个主键,用于唯一标识记录。
-
唯一约束保业务唯一:主键保证标识唯一,唯一约束保证业务逻辑唯一(如身份证号、邮箱)。
-
非空约束提质量:对核心业务字段尽量使用非空约束,配合默认值避免数据残缺。
-
外键约束慎权衡:
- 优点:保证参照完整性,防止"孤儿记录"。
- 缺点:影响性能,增加耦合,分库分表时可能不兼容。
- 建议:初期可用外键保证数据质量,后期根据性能评估决定是否保留。
-
组合使用示例:
mysql-- 用户表:主键 + 唯一 + 非空 + 默认值组合 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, -- 主键,自增 username VARCHAR(50) UNIQUE NOT NULL, -- 唯一且非空 email VARCHAR(100) UNIQUE, -- 唯一但可为空 status VARCHAR(10) NOT NULL DEFAULT 'active', -- 非空且有默认值 created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -- 非空且自动填充 ); -- 订单表与详情表:主键 + 外键 + 非空组合 CREATE TABLE orders ( order_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, -- 非空,应用层关联用户 total DECIMAL(10,2) NOT NULL DEFAULT 0.00, CONSTRAINT fk_orders_users FOREIGN KEY (user_id) REFERENCES users(id) -- 外键 ); CREATE TABLE order_items ( item_id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, -- 非空外键 product_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, CONSTRAINT fk_items_order FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE );
选择合适的约束并合理组合,能在保证数据完整性的同时,兼顾性能与扩展性。
约束命名规范建议
良好的约束命名规范能提升数据库脚本的可读性和可维护性。以下是常见的约束命名约定:
| 约束类型 | 命名前缀 | 命名格式 | 示例 |
|---|---|---|---|
| 主键约束 | pk_ |
pk_表名 |
pk_users、pk_orders |
| 唯一约束 | uk_ |
uk_表名_列名(多列用下划线连接) |
uk_users_email、uk_orders_user_order |
| 外键约束 | fk_ |
fk_子表_父表 或 fk_子表_列名 |
fk_order_items_orders、fk_order_items_product_id |
| 检查约束 | ck_ |
ck_表名_列名_规则 |
ck_products_price_positive |
| 默认约束 | df_ |
df_表名_列名 |
df_users_created_at |
命名规范示例
1. 主键约束命名
mysql
-- 创建表时显式命名主键
CREATE TABLE users (
user_id INT NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
CONSTRAINT pk_users PRIMARY KEY (user_id)
);
-- 添加主键时命名
ALTER TABLE orders ADD CONSTRAINT pk_orders PRIMARY KEY (order_id);
2. 唯一约束命名
mysql
-- 单列唯一约束
CREATE TABLE products (
product_id INT PRIMARY KEY,
sku VARCHAR(20) NOT NULL,
CONSTRAINT uk_products_sku UNIQUE (sku)
);
-- 多列联合唯一约束
CREATE TABLE user_roles (
user_id INT NOT NULL,
role_id INT NOT NULL,
CONSTRAINT uk_user_roles_user_role UNIQUE (user_id, role_id)
);
3. 外键约束命名
mysql
-- 清晰表达表间关系
CREATE TABLE order_items (
item_id INT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
CONSTRAINT fk_order_items_orders FOREIGN KEY (order_id) REFERENCES orders(order_id),
CONSTRAINT fk_order_items_products FOREIGN KEY (product_id) REFERENCES products(product_id)
);
-- 或使用列名作为后缀
CREATE TABLE comments (
comment_id INT PRIMARY KEY,
post_id INT NOT NULL,
user_id INT NOT NULL,
CONSTRAINT fk_comments_post_id FOREIGN KEY (post_id) REFERENCES posts(post_id),
CONSTRAINT fk_comments_user_id FOREIGN KEY (user_id) REFERENCES users(user_id)
);
命名规范的好处
- 可读性强:通过前缀即可识别约束类型
- 易于维护:在错误信息、数据库文档中能快速定位
- 避免冲突:系统生成的约束名通常无规律,自定义命名可避免重名
- 便于脚本管理:在迁移脚本、版本控制中更清晰
注意:MySQL 中非空约束和默认约束通常不单独命名,而是作为列定义的一部分。但其他数据库(如 SQL Server)支持为这些约束命名,保持命名一致性同样重要。