SQL内功笔记 · 第2篇:列的约束

约束类型对比

表格中横向对比了主键、唯一、非空、外键四种约束的核心区别:

约束类型 唯一性 非空性 数量限制 作用范围 备注
主键约束 (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 可能引发数据误删,需评估业务风险 • 高并发场景慎用:外键可能影响插入/更新性能,可考虑应用层保证一致性

综合设计原则

  1. 主键是基石:每张表都应有一个主键,用于唯一标识记录。

  2. 唯一约束保业务唯一:主键保证标识唯一,唯一约束保证业务逻辑唯一(如身份证号、邮箱)。

  3. 非空约束提质量:对核心业务字段尽量使用非空约束,配合默认值避免数据残缺。

  4. 外键约束慎权衡

    • 优点:保证参照完整性,防止"孤儿记录"。
    • 缺点:影响性能,增加耦合,分库分表时可能不兼容。
    • 建议:初期可用外键保证数据质量,后期根据性能评估决定是否保留。
  5. 组合使用示例

    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_userspk_orders
唯一约束 uk_ uk_表名_列名(多列用下划线连接) uk_users_emailuk_orders_user_order
外键约束 fk_ fk_子表_父表fk_子表_列名 fk_order_items_ordersfk_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)
);
命名规范的好处
  1. 可读性强:通过前缀即可识别约束类型
  2. 易于维护:在错误信息、数据库文档中能快速定位
  3. 避免冲突:系统生成的约束名通常无规律,自定义命名可避免重名
  4. 便于脚本管理:在迁移脚本、版本控制中更清晰

注意:MySQL 中非空约束和默认约束通常不单独命名,而是作为列定义的一部分。但其他数据库(如 SQL Server)支持为这些约束命名,保持命名一致性同样重要。

相关推荐
Engineer邓祥浩1 小时前
宏观认知(3):AI战略与社会影响——吴恩达《AI for Everyone》Week3学习笔记
人工智能·笔记·学习
java_cj1 小时前
MySQL 执行原理深度剖析:查询成本计算与优化器内幕
数据库·后端·mysql
java_cj1 小时前
数据库范式化设计与性能优化全攻略
数据库·后端·性能优化·架构·开源
Noushiki2 小时前
MySQL索引优化实战:高效查询的黄金法则
数据库·sql·mysql
TDengine (老段)2 小时前
TDengine Commit 与 Flush 机制 — 从内存到磁盘的数据落盘全流程
大数据·数据库·物联网·架构·时序数据库·iot·tdengine
ID_180079054732 小时前
(淘宝 / 京东)商品评论 API 接口:技术实战案例与架构分析
服务器·数据库·架构
爱莉希雅&&&2 小时前
Zabbix监控初步搭建
linux·运维·数据库·mysql·zabbix
狼与自由2 小时前
mysql到clickhouse
数据库·mysql·clickhouse
六月雨滴2 小时前
Oracle 数据库之归档日志
数据库·oracle·dba